Merge remote-tracking branch 'rem/v3' into v3
Some checks failed
pipeline / test (pull_request) Failing after 44s
pipeline / lint (pull_request) Failing after 10m2s

This commit is contained in:
Aleksandr Tolstikhin 2024-12-06 03:36:33 +07:00
commit a370f7e7b1
125 changed files with 3643 additions and 3301 deletions

3
.gitignore vendored
View File

@ -1,6 +1,8 @@
# Develop tools
/.vscode/
/.idea/
.idea
.vscode
# Binaries for programs and plugins
*.exe
@ -13,6 +15,7 @@
_obj
_test
_build
.DS_Store
# Architecture specific extensions/prefixes
*.[568vq]

View File

@ -1,9 +1,10 @@
// Package broker is an interface used for asynchronous messaging
package broker // import "go.unistack.org/micro/v3/broker"
package broker
import (
"context"
"errors"
"time"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
@ -17,6 +18,8 @@ var (
ErrNotConnected = errors.New("broker not connected")
// ErrDisconnected returns when broker disconnected
ErrDisconnected = errors.New("broker disconnected")
// DefaultGracefulTimeout
DefaultGracefulTimeout = 5 * time.Second
)
// Broker is an interface used for asynchronous messaging.
@ -43,8 +46,25 @@ type Broker interface {
BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
// String type of broker
String() string
// Live returns broker liveness
Live() bool
// Ready returns broker readiness
Ready() bool
// Health returns broker health
Health() bool
}
type (
FuncPublish func(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error
HookPublish func(next FuncPublish) FuncPublish
FuncBatchPublish func(ctx context.Context, msgs []*Message, opts ...PublishOption) error
HookBatchPublish func(next FuncBatchPublish) FuncBatchPublish
FuncSubscribe func(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
HookSubscribe func(next FuncSubscribe) FuncSubscribe
FuncBatchSubscribe func(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
HookBatchSubscribe func(next FuncBatchSubscribe) FuncBatchSubscribe
)
// Handler is used to process messages via a subscription of a topic.
type Handler func(Event) error
@ -74,6 +94,8 @@ type BatchHandler func(Events) error
// Event is given to a subscription handler for processing
type Event interface {
// Context return context.Context for event
Context() context.Context
// Topic returns event topic
Topic() string
// Message returns broker message

View File

@ -7,6 +7,7 @@ import (
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
maddr "go.unistack.org/micro/v3/util/addr"
"go.unistack.org/micro/v3/util/id"
mnet "go.unistack.org/micro/v3/util/net"
@ -14,9 +15,13 @@ import (
)
type memoryBroker struct {
subscribers map[string][]*memorySubscriber
addr string
opts broker.Options
funcPublish broker.FuncPublish
funcBatchPublish broker.FuncBatchPublish
funcSubscribe broker.FuncSubscribe
funcBatchSubscribe broker.FuncBatchSubscribe
subscribers map[string][]*memorySubscriber
addr string
opts broker.Options
sync.RWMutex
connected bool
}
@ -98,15 +103,42 @@ func (m *memoryBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&m.opts)
}
m.funcPublish = m.fnPublish
m.funcBatchPublish = m.fnBatchPublish
m.funcSubscribe = m.fnSubscribe
m.funcBatchSubscribe = m.fnBatchSubscribe
m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case broker.HookPublish:
m.funcPublish = h(m.funcPublish)
case broker.HookBatchPublish:
m.funcBatchPublish = h(m.funcBatchPublish)
case broker.HookSubscribe:
m.funcSubscribe = h(m.funcSubscribe)
case broker.HookBatchSubscribe:
m.funcBatchSubscribe = h(m.funcBatchSubscribe)
}
})
return nil
}
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
return m.funcPublish(ctx, topic, msg, opts...)
}
func (m *memoryBroker) fnPublish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
msg.Header.Set(metadata.HeaderTopic, topic)
return m.publish(ctx, []*broker.Message{msg}, opts...)
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return m.funcBatchPublish(ctx, msgs, opts...)
}
func (m *memoryBroker) fnBatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return m.publish(ctx, msgs, opts...)
}
@ -174,7 +206,7 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
}
} else if sub.opts.AutoAck {
if err = ms.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
m.opts.Logger.Error(m.opts.Context, "broker ack error", err)
}
}
// single processing
@ -185,11 +217,11 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
if eh != nil {
_ = eh(p)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
m.opts.Logger.Error(m.opts.Context, "broker handler error", err)
}
} else if sub.opts.AutoAck {
if err = p.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
m.opts.Logger.Error(m.opts.Context, "broker ack error", err)
}
}
}
@ -202,6 +234,10 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts
}
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return m.funcBatchSubscribe(ctx, topic, handler, opts...)
}
func (m *memoryBroker) fnBatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
@ -247,6 +283,10 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler
}
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return m.funcSubscribe(ctx, topic, handler, opts...)
}
func (m *memoryBroker) fnSubscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
@ -299,6 +339,18 @@ func (m *memoryBroker) Name() string {
return m.opts.Name
}
func (m *memoryBroker) Live() bool {
return true
}
func (m *memoryBroker) Ready() bool {
return true
}
func (m *memoryBroker) Health() bool {
return true
}
func (m *memoryEvent) Topic() string {
return m.topic
}
@ -333,6 +385,10 @@ func (m *memoryEvent) SetError(err error) {
m.err = err
}
func (m *memoryEvent) Context() context.Context {
return m.opts.Context
}
func (m *memorySubscriber) Options() broker.SubscribeOptions {
return m.opts
}

View File

@ -13,6 +13,10 @@ func TestMemoryBatchBroker(t *testing.T) {
b := NewBroker()
ctx := context.Background()
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error %v", err)
}
if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
@ -59,6 +63,10 @@ func TestMemoryBroker(t *testing.T) {
b := NewBroker()
ctx := context.Background()
if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error %v", err)
}
if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}

View File

@ -3,17 +3,40 @@ package broker
import (
"context"
"strings"
"go.unistack.org/micro/v3/options"
)
type NoopBroker struct {
opts Options
funcPublish FuncPublish
funcBatchPublish FuncBatchPublish
funcSubscribe FuncSubscribe
funcBatchSubscribe FuncBatchSubscribe
opts Options
}
func NewBroker(opts ...Option) *NoopBroker {
b := &NoopBroker{opts: NewOptions(opts...)}
b.funcPublish = b.fnPublish
b.funcBatchPublish = b.fnBatchPublish
b.funcSubscribe = b.fnSubscribe
b.funcBatchSubscribe = b.fnBatchSubscribe
return b
}
func (b *NoopBroker) Health() bool {
return true
}
func (b *NoopBroker) Live() bool {
return true
}
func (b *NoopBroker) Ready() bool {
return true
}
func (b *NoopBroker) Name() string {
return b.opts.Name
}
@ -30,6 +53,25 @@ func (b *NoopBroker) Init(opts ...Option) error {
for _, opt := range opts {
opt(&b.opts)
}
b.funcPublish = b.fnPublish
b.funcBatchPublish = b.fnBatchPublish
b.funcSubscribe = b.fnSubscribe
b.funcBatchSubscribe = b.fnBatchSubscribe
b.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookPublish:
b.funcPublish = h(b.funcPublish)
case HookBatchPublish:
b.funcBatchPublish = h(b.funcBatchPublish)
case HookSubscribe:
b.funcSubscribe = h(b.funcSubscribe)
case HookBatchSubscribe:
b.funcBatchSubscribe = h(b.funcBatchSubscribe)
}
})
return nil
}
@ -45,14 +87,22 @@ func (b *NoopBroker) Address() string {
return strings.Join(b.opts.Addrs, ",")
}
func (b *NoopBroker) BatchPublish(_ context.Context, _ []*Message, _ ...PublishOption) error {
func (b *NoopBroker) fnBatchPublish(_ context.Context, _ []*Message, _ ...PublishOption) error {
return nil
}
func (b *NoopBroker) Publish(_ context.Context, _ string, _ *Message, _ ...PublishOption) error {
func (b *NoopBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
return b.funcBatchPublish(ctx, msgs, opts...)
}
func (b *NoopBroker) fnPublish(_ context.Context, _ string, _ *Message, _ ...PublishOption) error {
return nil
}
func (b *NoopBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
return b.funcPublish(ctx, topic, msg, opts...)
}
type NoopSubscriber struct {
ctx context.Context
topic string
@ -61,14 +111,22 @@ type NoopSubscriber struct {
opts SubscribeOptions
}
func (b *NoopBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
func (b *NoopBroker) fnBatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
return &NoopSubscriber{ctx: ctx, topic: topic, opts: NewSubscribeOptions(opts...), batchHandler: handler}, nil
}
func (b *NoopBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
func (b *NoopBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
return b.funcBatchSubscribe(ctx, topic, handler, opts...)
}
func (b *NoopBroker) fnSubscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return &NoopSubscriber{ctx: ctx, topic: topic, opts: NewSubscribeOptions(opts...), handler: handler}, nil
}
func (b *NoopBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return b.funcSubscribe(ctx, topic, handler, opts...)
}
func (s *NoopSubscriber) Options() SubscribeOptions {
return s.opts
}
@ -77,6 +135,6 @@ func (s *NoopSubscriber) Topic() string {
return s.topic
}
func (s *NoopSubscriber) Unsubscribe(ctx context.Context) error {
func (s *NoopSubscriber) Unsubscribe(_ context.Context) error {
return nil
}

35
broker/noop_test.go Normal file
View File

@ -0,0 +1,35 @@
package broker
import (
"context"
"testing"
)
type testHook struct {
f bool
}
func (t *testHook) Publish1(fn FuncPublish) FuncPublish {
return func(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
t.f = true
return fn(ctx, topic, msg, opts...)
}
}
func TestNoopHook(t *testing.T) {
h := &testHook{}
b := NewBroker(Hooks(HookPublish(h.Publish1)))
if err := b.Init(); err != nil {
t.Fatal(err)
}
if err := b.Publish(context.TODO(), "", nil); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}

View File

@ -8,7 +8,9 @@ import (
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/sync"
"go.unistack.org/micro/v3/tracer"
)
@ -36,17 +38,25 @@ type Options struct {
Name string
// Addrs holds the broker address
Addrs []string
// Wait waits for a collection of goroutines to finish
Wait *sync.WaitGroup
// GracefulTimeout contains time to wait to finish in flight requests
GracefulTimeout time.Duration
// Hooks can be run before broker Publish/BatchPublish and
// Subscribe/BatchSubscribe methods
Hooks options.Hooks
}
// NewOptions create new Options
func NewOptions(opts ...Option) Options {
options := Options{
Register: register.DefaultRegister,
Logger: logger.DefaultLogger,
Context: context.Background(),
Meter: meter.DefaultMeter,
Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer,
Register: register.DefaultRegister,
Logger: logger.DefaultLogger,
Context: context.Background(),
Meter: meter.DefaultMeter,
Codec: codec.DefaultCodec,
Tracer: tracer.DefaultTracer,
GracefulTimeout: DefaultGracefulTimeout,
}
for _, o := range opts {
o(&options)
@ -224,6 +234,13 @@ func Name(n string) Option {
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}
// SubscribeContext set context
func SubscribeContext(ctx context.Context) SubscribeOption {
return func(o *SubscribeOptions) {

View File

@ -1,5 +1,5 @@
// Package client is an interface for an RPC client
package client // import "go.unistack.org/micro/v3/client"
package client
import (
"context"
@ -44,6 +44,17 @@ type Client interface {
String() string
}
type (
FuncCall func(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
HookCall func(next FuncCall) FuncCall
FuncStream func(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
HookStream func(next FuncStream) FuncStream
FuncPublish func(ctx context.Context, msg Message, opts ...PublishOption) error
HookPublish func(next FuncPublish) FuncPublish
FuncBatchPublish func(ctx context.Context, msg []Message, opts ...PublishOption) error
HookBatchPublish func(next FuncBatchPublish) FuncBatchPublish
)
// Message is the interface for publishing asynchronously
type Message interface {
Topic() string

View File

@ -1,26 +0,0 @@
package client
import (
"context"
"testing"
"time"
)
func TestNewClientCallOptions(t *testing.T) {
var flag bool
w := func(fn CallFunc) CallFunc {
flag = true
return fn
}
c := NewClientCallOptions(NewClient(),
WithAddress("127.0.0.1"),
WithCallWrapper(w),
WithRequestTimeout(1*time.Millisecond),
WithRetries(0),
WithBackoff(BackoffInterval(10*time.Millisecond, 100*time.Millisecond)),
)
_ = c.Call(context.TODO(), c.NewRequest("service", "endpoint", nil), nil)
if !flag {
t.Fatalf("NewClientCallOptions not works")
}
}

View File

@ -4,13 +4,17 @@ import (
"context"
"fmt"
"os"
"strconv"
"time"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/tracer"
)
// DefaultCodecs will be used to encode/decode data
@ -19,7 +23,11 @@ var DefaultCodecs = map[string]codec.Codec{
}
type noopClient struct {
opts Options
funcPublish FuncPublish
funcBatchPublish FuncBatchPublish
funcCall FuncCall
funcStream FuncStream
opts Options
}
type noopMessage struct {
@ -40,16 +48,14 @@ type noopRequest struct {
// NewClient returns new noop client
func NewClient(opts ...Option) Client {
nc := &noopClient{opts: NewOptions(opts...)}
// wrap in reverse
n := &noopClient{opts: NewOptions(opts...)}
c := Client(nc)
n.funcCall = n.fnCall
n.funcStream = n.fnStream
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
for i := len(nc.opts.Wrappers); i > 0; i-- {
c = nc.opts.Wrappers[i-1](c)
}
return c
return n
}
func (n *noopClient) Name() string {
@ -101,10 +107,13 @@ func (n *noopResponse) Read() ([]byte, error) {
return nil, nil
}
type noopStream struct{}
type noopStream struct {
err error
ctx context.Context
}
func (n *noopStream) Context() context.Context {
return context.Background()
return n.ctx
}
func (n *noopStream) Request() Request {
@ -132,15 +141,21 @@ func (n *noopStream) RecvMsg(interface{}) error {
}
func (n *noopStream) Error() error {
return nil
return n.err
}
func (n *noopStream) Close() error {
return nil
if sp, ok := tracer.SpanFromContext(n.ctx); ok && sp != nil {
if n.err != nil {
sp.SetStatus(tracer.SpanStatusError, n.err.Error())
}
sp.Finish()
}
return n.err
}
func (n *noopStream) CloseSend() error {
return nil
return n.err
}
func (n *noopMessage) Topic() string {
@ -173,6 +188,25 @@ func (n *noopClient) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
n.funcCall = n.fnCall
n.funcStream = n.fnStream
n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish
n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookCall:
n.funcCall = h(n.funcCall)
case HookStream:
n.funcStream = h(n.funcStream)
case HookPublish:
n.funcPublish = h(n.funcPublish)
case HookBatchPublish:
n.funcBatchPublish = h(n.funcBatchPublish)
}
})
return nil
}
@ -185,6 +219,31 @@ func (n *noopClient) String() string {
}
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
ts := time.Now()
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
var sp tracer.Span
ctx, sp = n.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels("endpoint", req.Endpoint()),
)
err := n.funcCall(ctx, req, rsp, opts...)
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
if me := errors.FromError(err); me == nil {
sp.Finish()
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc()
} else {
sp.SetStatus(tracer.SpanStatusError, err.Error())
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc()
}
return err
}
func (n *noopClient) fnCall(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
// make a copy of call opts
callOpts := n.opts.CallOptions
for _, opt := range opts {
@ -213,11 +272,8 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
}
// make copy of call method
hcall := n.call
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
hcall = callOpts.CallWrappers[i-1](hcall)
hcall := func(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil
}
// use the router passed as a call option, or fallback to the rpc clients router
@ -316,10 +372,6 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
return gerr
}
func (n *noopClient) call(ctx context.Context, addr string, req Request, rsp interface{}, opts CallOptions) error {
return nil
}
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{service: service, endpoint: endpoint}
}
@ -330,6 +382,31 @@ func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOp
}
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
ts := time.Now()
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
var sp tracer.Span
ctx, sp = n.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels("endpoint", req.Endpoint()),
)
stream, err := n.funcStream(ctx, req, opts...)
n.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
te := time.Since(ts)
n.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
n.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
if me := errors.FromError(err); me == nil {
sp.Finish()
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "success", "code", strconv.Itoa(int(200))).Inc()
} else {
sp.SetStatus(tracer.SpanStatusError, err.Error())
n.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", "failure", "code", strconv.Itoa(int(me.Code))).Inc()
}
return stream, err
}
func (n *noopClient) fnStream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
var err error
// make a copy of call opts
@ -470,14 +547,22 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
}
func (n *noopClient) stream(ctx context.Context, addr string, req Request, opts CallOptions) (Stream, error) {
return &noopStream{}, nil
return &noopStream{ctx: ctx}, nil
}
func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.funcBatchPublish(ctx, ps, opts...)
}
func (n *noopClient) fnBatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
return n.publish(ctx, ps, opts...)
}
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.funcPublish(ctx, p, opts...)
}
func (n *noopClient) fnPublish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.publish(ctx, []Message{p}, opts...)
}
@ -538,6 +623,13 @@ func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishO
msgs = append(msgs, &broker.Message{Header: md, Body: body})
}
if len(msgs) == 1 {
return n.opts.Broker.Publish(ctx, msgs[0].Header[metadata.HeaderTopic], msgs[0],
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),
)
}
return n.opts.Broker.BatchPublish(ctx, msgs,
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),

35
client/noop_test.go Normal file
View File

@ -0,0 +1,35 @@
package client
import (
"context"
"testing"
)
type testHook struct {
f bool
}
func (t *testHook) Publish(fn FuncPublish) FuncPublish {
return func(ctx context.Context, msg Message, opts ...PublishOption) error {
t.f = true
return fn(ctx, msg, opts...)
}
}
func TestNoopHook(t *testing.T) {
h := &testHook{}
c := NewClient(Hooks(HookPublish(h.Publish)))
if err := c.Init(); err != nil {
t.Fatal(err)
}
if err := c.Publish(context.TODO(), c.NewMessage("", nil, MessageContentType("application/octet-stream"))); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}

View File

@ -11,7 +11,7 @@ import (
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/selector"
@ -21,8 +21,6 @@ import (
// Options holds client options
type Options struct {
// Transport used for transfer messages
Transport transport.Transport
// Selector used to select needed address
Selector selector.Selector
// Logger used to log messages
@ -59,6 +57,9 @@ type Options struct {
PoolTTL time.Duration
// ContextDialer used to connect
ContextDialer func(context.Context, string) (net.Conn, error)
// Hooks can be run before broker Publish/BatchPublish and
// Subscribe/BatchSubscribe methods
Hooks options.Hooks
}
// NewCallOptions creates new call options struct
@ -92,8 +93,6 @@ type CallOptions struct {
Address []string
// SelectOptions selector options
SelectOptions []selector.SelectOption
// CallWrappers call wrappers
CallWrappers []CallWrapper
// StreamTimeout stream timeout
StreamTimeout time.Duration
// RequestTimeout request timeout
@ -185,25 +184,23 @@ func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
ContentType: DefaultContentType,
Codecs: make(map[string]codec.Codec),
Codecs: DefaultCodecs,
CallOptions: CallOptions{
Context: context.Background(),
Backoff: DefaultBackoff,
Retry: DefaultRetry,
Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout,
DialTimeout: transport.DefaultDialTimeout,
},
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
Transport: transport.DefaultTransport,
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
}
for _, o := range opts {
@ -276,13 +273,6 @@ func PoolTTL(d time.Duration) Option {
}
}
// Transport to use for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Register sets the routers register
func Register(r register.Register) Option {
return func(o *Options) {
@ -306,20 +296,6 @@ func Selector(s selector.Selector) Option {
}
}
// Wrap adds a wrapper to the list of options passed into the client
func Wrap(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}
// WrapCall adds a wrapper to the list of CallFunc wrappers
func WrapCall(cw ...CallWrapper) Option {
return func(o *Options) {
o.CallOptions.CallWrappers = append(o.CallOptions.CallWrappers, cw...)
}
}
// Backoff is used to set the backoff function used when retrying Calls
func Backoff(fn BackoffFunc) Option {
return func(o *Options) {
@ -346,14 +322,6 @@ func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
_ = o.Transport.Init(
transport.TLSConfig(t),
)
}
}
@ -450,13 +418,6 @@ func WithAddress(a ...string) CallOption {
}
}
// WithCallWrapper is a CallOption which adds to the existing CallFunc wrappers
func WithCallWrapper(cw ...CallWrapper) CallOption {
return func(o *CallOptions) {
o.CallWrappers = append(o.CallWrappers, cw...)
}
}
// WithBackoff is a CallOption which overrides that which
// set in Options.CallOptions
func WithBackoff(fn BackoffFunc) CallOption {
@ -526,13 +487,6 @@ func WithAuthToken(t string) CallOption {
}
}
// WithNetwork is a CallOption which sets the network attribute
func WithNetwork(n string) CallOption {
return func(o *CallOptions) {
o.Network = n
}
}
// WithRouter sets the router to use for this call
func WithRouter(r router.Router) CallOption {
return func(o *CallOptions) {
@ -591,3 +545,10 @@ func RequestContentType(ct string) RequestOption {
o.ContentType = ct
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

47
cluster/cluster.go Normal file
View File

@ -0,0 +1,47 @@
package cluster
import (
"context"
"go.unistack.org/micro/v3/metadata"
)
// Message sent to member in cluster
type Message interface {
// Header returns message headers
Header() metadata.Metadata
// Body returns broker message may be []byte slice or some go struct or interface
Body() interface{}
}
type Node interface {
// Name returns node name
Name() string
// Address returns node address
Address() string
// Metadata returns node metadata
Metadata() metadata.Metadata
}
// Cluster interface used for cluster communication across nodes
type Cluster interface {
// Join is used to take an existing members and performing state sync
Join(ctx context.Context, addr ...string) error
// Leave broadcast a leave message and stop listeners
Leave(ctx context.Context) error
// Ping is used to probe live status of the node
Ping(ctx context.Context, node Node, payload []byte) error
// Members returns the cluster members
Members() ([]Node, error)
// Broadcast send message for all members in cluster, if filter is not nil, nodes may be filtered
// by key/value pairs
Broadcast(ctx context.Context, msg Message, filter ...string) error
// Unicast send message to single member in cluster
Unicast(ctx context.Context, node Node, msg Message) error
// Live returns cluster liveness
Live() bool
// Ready returns cluster readiness
Ready() bool
// Health returns cluster health
Health() bool
}

View File

@ -1,19 +1,8 @@
// Package codec is an interface for encoding messages
package codec // import "go.unistack.org/micro/v3/codec"
package codec
import (
"errors"
"io"
"go.unistack.org/micro/v3/metadata"
)
// Message types
const (
Error MessageType = iota
Request
Response
Event
)
var (
@ -24,65 +13,23 @@ var (
)
var (
// DefaultMaxMsgSize specifies how much data codec can handle
DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec
DefaultCodec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec"
)
// MessageType specifies message type for codec
type MessageType int
// Codec encodes/decodes various types of messages used within micro.
// ReadHeader and ReadBody are called in pairs to read requests/responses
// from the connection. Close is called when finished with the
// connection. ReadBody may be called with a nil argument to force the
// body to be read and discarded.
// Codec encodes/decodes various types of messages.
type Codec interface {
ReadHeader(r io.Reader, m *Message, mt MessageType) error
ReadBody(r io.Reader, v interface{}) error
Write(w io.Writer, m *Message, v interface{}) error
Marshal(v interface{}, opts ...Option) ([]byte, error)
Unmarshal(b []byte, v interface{}, opts ...Option) error
String() string
}
// Message represents detailed information about
// the communication, likely followed by the body.
// In the case of an error, body may be nil.
type Message struct {
Header metadata.Metadata
Target string
Method string
Endpoint string
Error string
ID string
Body []byte
Type MessageType
}
// NewMessage creates new codec message
func NewMessage(t MessageType) *Message {
return &Message{Type: t, Header: metadata.New(0)}
}
// MarshalAppend calls codec.Marshal(v) and returns the data appended to buf.
// If codec implements MarshalAppend, that is called instead.
func MarshalAppend(buf []byte, c Codec, v interface{}, opts ...Option) ([]byte, error) {
if nc, ok := c.(interface {
MarshalAppend([]byte, interface{}, ...Option) ([]byte, error)
}); ok {
return nc.MarshalAppend(buf, v, opts...)
}
mbuf, err := c.Marshal(v, opts...)
if err != nil {
return nil, err
}
return append(buf, mbuf...), nil
type CodecV2 interface {
Marshal(buf []byte, v interface{}, opts ...Option) ([]byte, error)
Unmarshal(buf []byte, v interface{}, opts ...Option) error
String() string
}
// RawMessage is a raw encoded JSON value.
@ -93,6 +40,8 @@ type RawMessage []byte
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
} else if len(*m) == 0 {
return []byte("null"), nil
}
return *m, nil
}

View File

@ -2,70 +2,14 @@ package codec
import (
"encoding/json"
"io"
codecpb "go.unistack.org/micro-proto/v3/codec"
)
type noopCodec struct {
opts Options
}
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil
}
func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error {
// read bytes
buf, err := io.ReadAll(conn)
if err != nil {
return err
}
if b == nil {
return nil
}
switch v := b.(type) {
case *string:
*v = string(buf)
case *[]byte:
*v = buf
case *Frame:
v.Data = buf
default:
return json.Unmarshal(buf, v)
}
return nil
}
func (c *noopCodec) Write(conn io.Writer, m *Message, b interface{}) error {
if b == nil {
return nil
}
var v []byte
switch vb := b.(type) {
case *Frame:
v = vb.Data
case string:
v = []byte(vb)
case *string:
v = []byte(*vb)
case *[]byte:
v = *vb
case []byte:
v = vb
default:
var err error
v, err = json.Marshal(vb)
if err != nil {
return err
}
}
_, err := conn.Write(v)
return err
}
func (c *noopCodec) String() string {
return "noop"
}
@ -91,8 +35,8 @@ func (c *noopCodec) Marshal(v interface{}, opts ...Option) ([]byte, error) {
return ve, nil
case *Frame:
return ve.Data, nil
case *Message:
return ve.Body, nil
case *codecpb.Frame:
return ve.Data, nil
}
return json.Marshal(v)
@ -115,8 +59,8 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}, opts ...Option) error {
case *Frame:
ve.Data = d
return nil
case *Message:
ve.Body = d
case *codecpb.Frame:
ve.Data = d
return nil
}

View File

@ -23,15 +23,8 @@ type Options struct {
Context context.Context
// TagName specifies tag name in struct to control codec
TagName string
// MaxMsgSize specifies max messages size that reads by codec
MaxMsgSize int
}
// MaxMsgSize sets the max message size
func MaxMsgSize(n int) Option {
return func(o *Options) {
o.MaxMsgSize = n
}
// Flatten specifies that struct must be analyzed for flatten tag
Flatten bool
}
// TagName sets the codec tag name in struct
@ -41,6 +34,13 @@ func TagName(n string) Option {
}
}
// Flatten enables checking for flatten tag name
func Flatten(b bool) Option {
return func(o *Options) {
o.Flatten = b
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
@ -65,12 +65,12 @@ func Meter(m meter.Meter) Option {
// NewOptions returns new options
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
MaxMsgSize: DefaultMaxMsgSize,
TagName: DefaultTagName,
Context: context.Background(),
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
TagName: DefaultTagName,
Flatten: false,
}
for _, o := range opts {

View File

@ -1,5 +1,5 @@
// Package config is an interface for dynamic configuration.
package config // import "go.unistack.org/micro/v3/config"
package config
import (
"context"
@ -50,6 +50,13 @@ type Config interface {
String() string
}
type (
FuncLoad func(ctx context.Context, opts ...LoadOption) error
HookLoad func(next FuncLoad) FuncLoad
FuncSave func(ctx context.Context, opts ...SaveOption) error
HookSave func(next FuncSave) FuncSave
)
// Watcher is the config watcher
type Watcher interface {
// Next blocks until update happens or error returned
@ -131,7 +138,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeLoad err: %v", c.String(), err)
c.Options().Logger.Error(ctx, c.String()+" BeforeLoad error", err)
if !c.Options().AllowFail {
return err
}
@ -146,7 +153,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s AfterLoad err: %v", c.String(), err)
c.Options().Logger.Error(ctx, c.String()+" AfterLoad error", err)
if !c.Options().AllowFail {
return err
}
@ -161,7 +168,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeSave err: %v", c.String(), err)
c.Options().Logger.Error(ctx, c.String()+" BeforeSave error", err)
if !c.Options().AllowFail {
return err
}
@ -176,7 +183,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s AfterSave err: %v", c.String(), err)
c.Options().Logger.Error(ctx, c.String()+" AfterSave error", err)
if !c.Options().AllowFail {
return err
}
@ -191,7 +198,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeInit err: %v", c.String(), err)
c.Options().Logger.Error(ctx, c.String()+" BeforeInit error", err)
if !c.Options().AllowFail {
return err
}
@ -206,7 +213,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s AfterInit err: %v", c.String(), err)
c.Options().Logger.Error(ctx, c.String()+" AfterInit error", err)
if !c.Options().AllowFail {
return err
}

View File

@ -9,13 +9,16 @@ import (
"dario.cat/mergo"
"github.com/google/uuid"
"go.unistack.org/micro/v3/options"
mid "go.unistack.org/micro/v3/util/id"
rutil "go.unistack.org/micro/v3/util/reflect"
mtime "go.unistack.org/micro/v3/util/time"
)
type defaultConfig struct {
opts Options
funcLoad FuncLoad
funcSave FuncSave
opts Options
}
func (c *defaultConfig) Options() Options {
@ -31,6 +34,18 @@ func (c *defaultConfig) Init(opts ...Option) error {
return err
}
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
c.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookLoad:
c.funcLoad = h(c.funcLoad)
case HookSave:
c.funcSave = h(c.funcSave)
}
})
if err := DefaultAfterInit(c.opts.Context, c); err != nil && !c.opts.AllowFail {
return err
}
@ -39,11 +54,17 @@ func (c *defaultConfig) Init(opts ...Option) error {
}
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
return c.funcLoad(ctx, opts...)
}
func (c *defaultConfig) fnLoad(ctx context.Context, opts ...LoadOption) error {
var err error
if c.opts.SkipLoad != nil && c.opts.SkipLoad(ctx, c) {
return nil
}
if err := DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
if err = DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
return err
}
@ -233,6 +254,7 @@ func fillValue(value reflect.Value, val string) error {
}
value.Set(reflect.ValueOf(v))
}
return nil
}
@ -295,7 +317,11 @@ func fillValues(valueOf reflect.Value, tname string) error {
return nil
}
func (c *defaultConfig) Save(ctx context.Context, _ ...SaveOption) error {
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
return c.funcSave(ctx, opts...)
}
func (c *defaultConfig) fnSave(ctx context.Context, opts ...SaveOption) error {
if c.opts.SkipSave != nil && c.opts.SkipSave(ctx, c) {
return nil
}
@ -319,7 +345,7 @@ func (c *defaultConfig) Name() string {
return c.opts.Name
}
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
func (c *defaultConfig) Watch(_ context.Context, _ ...WatchOption) (Watcher, error) {
return nil, ErrWatcherNotImplemented
}
@ -329,5 +355,9 @@ func NewConfig(opts ...Option) Config {
if len(options.StructTag) == 0 {
options.StructTag = "default"
}
return &defaultConfig{opts: options}
c := &defaultConfig{opts: options}
c.funcLoad = c.fnLoad
c.funcSave = c.fnSave
return c
}

View File

@ -41,6 +41,35 @@ func (c *cfgStructValue) Validate() error {
return nil
}
type testHook struct {
f bool
}
func (t *testHook) Load(fn config.FuncLoad) config.FuncLoad {
return func(ctx context.Context, opts ...config.LoadOption) error {
t.f = true
return fn(ctx, opts...)
}
}
func TestHook(t *testing.T) {
h := &testHook{}
c := config.NewConfig(config.Struct(h), config.Hooks(config.HookLoad(h.Load)))
if err := c.Init(); err != nil {
t.Fatal(err)
}
if err := c.Load(context.TODO()); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}
func TestDefault(t *testing.T) {
ctx := context.Background()
conf := &cfg{IntValue: 10}

View File

@ -7,6 +7,7 @@ import (
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/tracer"
)
@ -46,6 +47,8 @@ type Options struct {
SkipLoad func(context.Context, Config) bool
// SkipSave runs only if condition returns true
SkipSave func(context.Context, Config) bool
// Hooks can be run before/after config Save/Load
Hooks options.Hooks
}
// Option function signature
@ -288,3 +291,10 @@ func WatchStruct(src interface{}) WatchOption {
o.Struct = src
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

View File

@ -1,6 +1,6 @@
// Package errors provides a way to return detailed information
// for an RPC request error. The error is normally JSON encoded.
package errors // import "go.unistack.org/micro/v3/errors"
package errors
import (
"bytes"
@ -44,6 +44,20 @@ var (
ErrGatewayTimeout = &Error{Code: 504}
)
const ProblemContentType = "application/problem+json"
type Problem struct {
Type string `json:"type,omitempty"`
Title string `json:"title,omitempty"`
Detail string `json:"detail,omitempty"`
Instance string `json:"instance,omitempty"`
Errors []struct {
Title string `json:"title,omitempty"`
Detail string `json:"detail,omitempty"`
} `json:"errors,omitempty"`
Status int `json:"status,omitempty"`
}
// Error type
type Error struct {
// ID holds error id or service, usually someting like my_service or id
@ -262,6 +276,10 @@ func CodeIn(err interface{}, codes ...int32) bool {
// FromError try to convert go error to *Error
func FromError(err error) *Error {
if err == nil {
return nil
}
if verr, ok := err.(*Error); ok && verr != nil {
return verr
}

View File

@ -188,7 +188,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
steps, err := w.getSteps(options.Start, options.Reverse)
if err != nil {
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
w.opts.Logger.Error(w.opts.Context, "store write error", werr)
}
return "", err
}
@ -212,7 +212,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
done := make(chan struct{})
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
w.opts.Logger.Error(w.opts.Context, "store write error", werr)
return eid, werr
}
for idx := range steps {
@ -237,7 +237,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return
}
if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
w.opts.Logger.Trace(nctx, fmt.Sprintf("will be executed %v", steps[idx][nidx]))
}
cstep := steps[idx][nidx]
// nolint: nestif
@ -257,21 +257,21 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
if serr != nil {
step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
}
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
}
cherr <- serr
return
}
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, step.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
cherr <- werr
return
}
@ -290,16 +290,16 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
if serr != nil {
cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
}
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
}
cherr <- serr
return
}
if werr := stepStore.Write(ctx, cstep.ID()+w.opts.Store.Options().Separator+"rsp", rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
w.opts.Logger.Error(ctx, "store write error", werr)
cherr <- werr
return
}
@ -317,7 +317,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
return eid, nil
}
logger.Tracef(ctx, "wait for finish or error")
logger.DefaultLogger.Trace(ctx, "wait for finish or error")
select {
case <-nctx.Done():
err = nctx.Err()
@ -333,15 +333,15 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
switch {
case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
w.opts.Logger.Error(w.opts.Context, "store write error", werr)
}
case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
w.opts.Logger.Error(w.opts.Context, "store write error", werr)
}
case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
w.opts.Logger.Error(w.opts.Context, "store write error", werr)
}
}

View File

@ -1,5 +1,5 @@
// Package flow is an interface used for saga pattern microservice workflow
package flow // import "go.unistack.org/micro/v3/flow"
package flow
import (
"context"

View File

@ -1,4 +1,4 @@
package fsm // import "go.unistack.org/micro/v3/fsm"
package fsm
import (
"context"

View File

@ -17,7 +17,7 @@ func TestFSMStart(t *testing.T) {
wrapper := func(next StateFunc) StateFunc {
return func(sctx context.Context, s State, opts ...StateOption) (State, error) {
sctx = logger.NewContext(sctx, logger.Fields("state", s.Name()))
sctx = logger.NewContext(sctx, logger.DefaultLogger.Fields("state", s.Name()))
return next(sctx, s, opts...)
}
}

16
go.mod
View File

@ -1,20 +1,32 @@
module go.unistack.org/micro/v3
go 1.20
go 1.22
require (
dario.cat/mergo v1.0.0
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/KimMachineGun/automemlimit v0.6.1
github.com/google/uuid v1.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
go.uber.org/automaxprocs v1.6.0
go.unistack.org/micro-proto/v3 v3.4.1
golang.org/x/sync v0.3.0
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.31.0
google.golang.org/protobuf v1.33.0
)
require (
github.com/cilium/ebpf v0.9.1 // indirect
github.com/containerd/cgroups/v3 v3.0.1 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect
)

49
go.sum
View File

@ -2,23 +2,68 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
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/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY=
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
github.com/containerd/cgroups/v3 v3.0.1 h1:4hfGvu8rfGIwVIDd+nLzn/B9ZXx4BcCjzt5ToenJRaE=
github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q=
go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
@ -26,8 +71,8 @@ google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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=

View File

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

View File

@ -1,5 +1,5 @@
// Package logger provides a log interface
package logger // import "go.unistack.org/micro/v3/logger"
package logger
import (
"context"
@ -14,8 +14,6 @@ var (
DefaultLogger Logger = NewLogger()
// DefaultLevel used by logger
DefaultLevel = InfoLevel
// DefaultCallerSkipCount used by logger
DefaultCallerSkipCount = 2
)
// Logger is a generic logging interface
@ -33,33 +31,19 @@ type Logger interface {
// Fields set fields to always be logged with keyval pairs
Fields(fields ...interface{}) Logger
// Info level message
Info(ctx context.Context, args ...interface{})
Info(ctx context.Context, msg string, args ...interface{})
// Trace level message
Trace(ctx context.Context, args ...interface{})
Trace(ctx context.Context, msg string, args ...interface{})
// Debug level message
Debug(ctx context.Context, args ...interface{})
Debug(ctx context.Context, msg string, args ...interface{})
// Warn level message
Warn(ctx context.Context, args ...interface{})
Warn(ctx context.Context, msg string, args ...interface{})
// Error level message
Error(ctx context.Context, args ...interface{})
Error(ctx context.Context, msg string, args ...interface{})
// Fatal level message
Fatal(ctx context.Context, args ...interface{})
// Infof level message
Infof(ctx context.Context, msg string, args ...interface{})
// Tracef level message
Tracef(ctx context.Context, msg string, args ...interface{})
// Debug level message
Debugf(ctx context.Context, msg string, args ...interface{})
// Warn level message
Warnf(ctx context.Context, msg string, args ...interface{})
// Error level message
Errorf(ctx context.Context, msg string, args ...interface{})
// Fatal level message
Fatalf(ctx context.Context, msg string, args ...interface{})
Fatal(ctx context.Context, msg string, args ...interface{})
// Log logs message with needed level
Log(ctx context.Context, level Level, args ...interface{})
// Logf logs message with needed level
Logf(ctx context.Context, level Level, msg string, args ...interface{})
Log(ctx context.Context, level Level, msg string, args ...interface{})
// Name returns broker instance name
Name() string
// String returns the type of logger
@ -68,108 +52,3 @@ type Logger interface {
// Field contains keyval pair
type Field interface{}
// Info writes msg to default logger on info level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Info(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Info(ctx, args...)
}
// Error writes msg to default logger on error level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Error(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Error(ctx, args...)
}
// Debug writes msg to default logger on debug level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Debug(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debug(ctx, args...)
}
// Warn writes msg to default logger on warn level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Warn(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warn(ctx, args...)
}
// Trace writes msg to default logger on trace level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Trace(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Trace(ctx, args...)
}
// Fatal writes msg to default logger on fatal level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Fatal(ctx context.Context, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatal(ctx, args...)
}
// Infof writes formatted msg to default logger on info level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Infof(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Infof(ctx, msg, args...)
}
// Errorf writes formatted msg to default logger on error level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Errorf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Errorf(ctx, msg, args...)
}
// Debugf writes formatted msg to default logger on debug level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Debugf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Debugf(ctx, msg, args...)
}
// Warnf writes formatted msg to default logger on warn level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Warnf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Warnf(ctx, msg, args...)
}
// Tracef writes formatted msg to default logger on trace level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Tracef(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Tracef(ctx, msg, args...)
}
// Fatalf writes formatted msg to default logger on fatal level
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Fatalf(ctx context.Context, msg string, args ...interface{}) {
DefaultLogger.Clone(WithCallerSkipCount(DefaultCallerSkipCount+1)).Fatalf(ctx, msg, args...)
}
// V returns true if passed level enabled in default logger
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func V(level Level) bool {
return DefaultLogger.V(level)
}
// Init initialize logger
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Init(opts ...Option) error {
return DefaultLogger.Init(opts...)
}
// Fields create logger with specific fields
//
// Deprecated: Dont use logger methods directly, use instance of logger to avoid additional allocations
func Fields(fields ...interface{}) Logger {
return DefaultLogger.Fields(fields...)
}

View File

@ -4,12 +4,17 @@ import (
"context"
)
const (
defaultCallerSkipCount = 2
)
type noopLogger struct {
opts Options
}
func NewLogger(opts ...Option) Logger {
options := NewOptions(opts...)
options.CallerSkipCount = defaultCallerSkipCount
return &noopLogger{opts: options}
}
@ -51,44 +56,23 @@ func (l *noopLogger) String() string {
return "noop"
}
func (l *noopLogger) Log(ctx context.Context, lvl Level, attrs ...interface{}) {
func (l *noopLogger) Log(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Info(ctx context.Context, attrs ...interface{}) {
func (l *noopLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Debug(ctx context.Context, attrs ...interface{}) {
func (l *noopLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Error(ctx context.Context, attrs ...interface{}) {
func (l *noopLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Trace(ctx context.Context, attrs ...interface{}) {
func (l *noopLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Warn(ctx context.Context, attrs ...interface{}) {
func (l *noopLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Fatal(ctx context.Context, attrs ...interface{}) {
}
func (l *noopLogger) Logf(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Infof(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Debugf(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Errorf(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Tracef(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{}) {
func (l *noopLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
}

View File

@ -6,6 +6,8 @@ import (
"log/slog"
"os"
"time"
"go.unistack.org/micro/v3/meter"
)
// Option func signature
@ -21,7 +23,7 @@ type Options struct {
Name string
// Fields holds additional metadata
Fields []interface{}
// CallerSkipCount number of frmaes to skip
// callerSkipCount number of frmaes to skip
CallerSkipCount int
// ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc
@ -45,6 +47,8 @@ type Options struct {
Level Level
// TimeFunc used to obtain current time
TimeFunc func() time.Time
// Meter used to count logs for specific level
Meter meter.Meter
}
// NewOptions creates new options struct
@ -53,11 +57,11 @@ func NewOptions(opts ...Option) Options {
Level: DefaultLevel,
Fields: make([]interface{}, 0, 6),
Out: os.Stderr,
CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(),
ContextAttrFuncs: DefaultContextAttrFuncs,
AddSource: true,
TimeFunc: time.Now,
Meter: meter.DefaultMeter,
}
WithMicroKeys()(&options)
@ -69,13 +73,20 @@ func NewOptions(opts ...Option) Options {
return options
}
// WithContextAttrFuncs appends default funcs for the context arrts filler
// WithContextAttrFuncs appends default funcs for the context attrs filler
func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
return func(o *Options) {
o.ContextAttrFuncs = append(o.ContextAttrFuncs, fncs...)
}
}
// WithAddFields add fields for the logger
func WithAddFields(fields ...interface{}) Option {
return func(o *Options) {
o.Fields = append(o.Fields, fields...)
}
}
// WithFields set default fields for the logger
func WithFields(fields ...interface{}) Option {
return func(o *Options) {
@ -97,27 +108,20 @@ func WithOutput(out io.Writer) Option {
}
}
// WitAddStacktrace controls writing stacktrace on error
// WithAddStacktrace controls writing stacktrace on error
func WithAddStacktrace(v bool) Option {
return func(o *Options) {
o.AddStacktrace = v
}
}
// WitAddSource controls writing source file and pos in log
// WithAddSource controls writing source file and pos in log
func WithAddSource(v bool) Option {
return func(o *Options) {
o.AddSource = v
}
}
// WithCallerSkipCount set frame count to skip
func WithCallerSkipCount(c int) Option {
return func(o *Options) {
o.CallerSkipCount = c
}
}
// WithContext set context
func WithContext(ctx context.Context) Option {
return func(o *Options) {
@ -132,6 +136,13 @@ func WithName(n string) Option {
}
}
// WithMeter sets the meter
func WithMeter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// WithTimeFunc sets the func to obtain current time
func WithTimeFunc(fn func() time.Time) Option {
return func(o *Options) {
@ -182,3 +193,12 @@ func WithMicroKeys() Option {
o.ErrorKey = "error"
}
}
// WithAddCallerSkipCount add skip count for copy logger
func WithAddCallerSkipCount(n int) Option {
return func(o *Options) {
if n > 0 {
o.CallerSkipCount += n
}
}
}

View File

@ -2,18 +2,25 @@ package slog
import (
"context"
"fmt"
"log/slog"
"os"
"regexp"
"runtime"
"strconv"
"sync"
"sync/atomic"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/semconv"
"go.unistack.org/micro/v3/tracer"
)
const (
badKey = "!BADKEY"
// defaultCallerSkipCount used by logger
defaultCallerSkipCount = 3
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
var (
@ -25,6 +32,27 @@ var (
fatalValue = slog.StringValue("fatal")
)
type wrapper struct {
h slog.Handler
level atomic.Int64
}
func (h *wrapper) Enabled(ctx context.Context, level slog.Level) bool {
return level >= slog.Level(int(h.level.Load()))
}
func (h *wrapper) Handle(ctx context.Context, rec slog.Record) error {
return h.h.Handle(ctx, rec)
}
func (h *wrapper) WithAttrs(attrs []slog.Attr) slog.Handler {
return h.WithAttrs(attrs)
}
func (h *wrapper) WithGroup(name string) slog.Handler {
return h.WithGroup(name)
}
func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.SourceKey:
@ -62,7 +90,7 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
type slogLogger struct {
leveler *slog.LevelVar
handler slog.Handler
handler *wrapper
opts logger.Options
mu sync.RWMutex
}
@ -76,51 +104,52 @@ func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
o(&options)
}
l := &slogLogger{
opts: options,
if len(options.ContextAttrFuncs) == 0 {
options.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
l.leveler = new(slog.LevelVar)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: l.renameAttr,
Level: l.leveler,
AddSource: l.opts.AddSource,
attrs, _ := s.argsAttrs(options.Fields)
l := &slogLogger{
handler: &wrapper{h: s.handler.h.WithAttrs(attrs)},
opts: options,
}
l.leveler.Set(loggerToSlogLevel(l.opts.Level))
l.handler = slog.New(slog.NewJSONHandler(options.Out, handleOpt)).With(options.Fields...).Handler()
l.handler.level.Store(int64(loggerToSlogLevel(options.Level)))
return l
}
func (s *slogLogger) V(level logger.Level) bool {
return s.opts.Level.Enabled(level)
s.mu.Lock()
v := s.opts.Level.Enabled(level)
s.mu.Unlock()
return v
}
func (s *slogLogger) Level(level logger.Level) {
s.leveler.Set(loggerToSlogLevel(level))
s.mu.Lock()
s.opts.Level = level
s.handler.level.Store(int64(loggerToSlogLevel(level)))
s.mu.Unlock()
}
func (s *slogLogger) Options() logger.Options {
return s.opts
}
func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger {
func (s *slogLogger) Fields(fields ...interface{}) logger.Logger {
s.mu.RLock()
level := s.leveler.Level()
options := s.opts
s.mu.RUnlock()
l := &slogLogger{opts: options}
l.leveler = new(slog.LevelVar)
l.leveler.Set(level)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: l.renameAttr,
Level: l.leveler,
AddSource: l.opts.AddSource,
if len(options.ContextAttrFuncs) == 0 {
options.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
l.handler = slog.New(slog.NewJSONHandler(l.opts.Out, handleOpt)).With(attrs...).Handler()
attrs, _ := s.argsAttrs(fields)
l.handler = &wrapper{h: s.handler.h.WithAttrs(attrs)}
l.handler.level.Store(int64(loggerToSlogLevel(l.opts.Level)))
return l
}
@ -128,393 +157,55 @@ func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger {
func (s *slogLogger) Init(opts ...logger.Option) error {
s.mu.Lock()
if len(s.opts.ContextAttrFuncs) == 0 {
s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
for _, o := range opts {
o(&s.opts)
}
s.leveler = new(slog.LevelVar)
if len(s.opts.ContextAttrFuncs) == 0 {
s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
handleOpt := &slog.HandlerOptions{
ReplaceAttr: s.renameAttr,
Level: s.leveler,
Level: loggerToSlogLevel(logger.TraceLevel),
AddSource: s.opts.AddSource,
}
s.leveler.Set(loggerToSlogLevel(s.opts.Level))
s.handler = slog.New(slog.NewJSONHandler(s.opts.Out, handleOpt)).With(s.opts.Fields...).Handler()
attrs, _ := s.argsAttrs(s.opts.Fields)
s.handler = &wrapper{h: slog.NewJSONHandler(s.opts.Out, handleOpt).WithAttrs(attrs)}
s.handler.level.Store(int64(loggerToSlogLevel(s.opts.Level)))
s.mu.Unlock()
return nil
}
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, attrs ...interface{}) {
if !s.V(lvl) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs[1:]...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
s.printLog(ctx, lvl, msg, attrs...)
}
func (s *slogLogger) Logf(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
if !s.V(lvl) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, (slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1])))
}
}
}
r.Add(attrs[1:]...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.InfoLevel, msg, attrs...)
}
func (s *slogLogger) Info(ctx context.Context, attrs ...interface{}) {
if !s.V(logger.InfoLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelInfo, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.DebugLevel, msg, attrs...)
}
func (s *slogLogger) Infof(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(logger.InfoLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelInfo, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.TraceLevel, msg, attrs...)
}
func (s *slogLogger) Debug(ctx context.Context, attrs ...interface{}) {
if !s.V(logger.DebugLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.ErrorLevel, msg, attrs...)
}
func (s *slogLogger) Debugf(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(logger.DebugLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Trace(ctx context.Context, attrs ...interface{}) {
if !s.V(logger.TraceLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug-1, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Tracef(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(logger.TraceLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelDebug-1, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) {
if !s.V(logger.ErrorLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs[1:]...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(logger.ErrorLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
if s.opts.AddStacktrace {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
r.Add(attrs...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == s.opts.ErrorKey {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Fatal(ctx context.Context, attrs ...interface{}) {
if !s.V(logger.FatalLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError+1, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.FatalLevel, msg, attrs...)
os.Exit(1)
}
func (s *slogLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(logger.FatalLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelError+1, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs...)
_ = s.handler.Handle(ctx, r)
os.Exit(1)
}
func (s *slogLogger) Warn(ctx context.Context, attrs ...interface{}) {
if !s.V(logger.WarnLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelWarn, fmt.Sprintf("%s", attrs[0]), pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
}
func (s *slogLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(logger.WarnLevel) {
return
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(s.opts.TimeFunc(), slog.LevelWarn, msg, pcs[0])
for _, fn := range s.opts.ContextAttrFuncs {
attrs = append(attrs, fn(ctx)...)
}
for idx, attr := range attrs {
if ve, ok := attr.(error); ok && ve != nil {
attrs[idx] = slog.String(s.opts.ErrorKey, ve.Error())
break
}
}
r.Add(attrs[1:]...)
_ = s.handler.Handle(ctx, r)
func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.WarnLevel, msg, attrs...)
}
func (s *slogLogger) Name() string {
@ -525,10 +216,59 @@ func (s *slogLogger) String() string {
return "slog"
}
func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string, args ...interface{}) {
if !s.V(lvl) {
return
}
var argError error
s.opts.Meter.Counter(semconv.LoggerMessageTotal, "level", lvl.String()).Inc()
attrs, err := s.argsAttrs(args)
if err != nil {
argError = err
}
if argError != nil {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, argError.Error())
}
}
for _, fn := range s.opts.ContextAttrFuncs {
ctxAttrs, err := s.argsAttrs(fn(ctx))
if err != nil {
argError = err
}
attrs = append(attrs, ctxAttrs...)
}
if argError != nil {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, argError.Error())
}
}
if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
attrs = append(attrs, slog.String(s.opts.StacktraceKey, traceLines[len(traceLines)-1]))
}
}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, printLog, LogLvlMethod]
r := slog.NewRecord(s.opts.TimeFunc(), loggerToSlogLevel(lvl), msg, pcs[0])
r.AddAttrs(attrs...)
_ = s.handler.Handle(ctx, r)
}
func NewLogger(opts ...logger.Option) logger.Logger {
s := &slogLogger{
opts: logger.NewOptions(opts...),
}
s.opts.CallerSkipCount = defaultCallerSkipCount
return s
}
@ -566,3 +306,27 @@ func slogToLoggerLevel(level slog.Level) logger.Level {
return logger.InfoLevel
}
}
func (s *slogLogger) argsAttrs(args []interface{}) ([]slog.Attr, error) {
attrs := make([]slog.Attr, 0, len(args))
var err error
for idx := 0; idx < len(args); idx++ {
switch arg := args[idx].(type) {
case slog.Attr:
attrs = append(attrs, arg)
case string:
if idx+1 < len(args) {
attrs = append(attrs, slog.Any(arg, args[idx+1]))
idx += 1
} else {
attrs = append(attrs, slog.String(badKey, arg))
}
case error:
attrs = append(attrs, slog.String(s.opts.ErrorKey, arg.Error()))
err = arg
}
}
return attrs, err
}

View File

@ -3,13 +3,96 @@ package slog
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"strings"
"testing"
"github.com/google/uuid"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/logger"
)
func TestWithAddFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg1")
if err := l.Init(logger.WithAddFields("key1", "val1")); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg2")
if err := l.Init(logger.WithAddFields("key2", "val2")); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg3")
if !bytes.Contains(buf.Bytes(), []byte(`"key1"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"key2"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestMultipleFieldsWithLevel(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l = l.Fields("key", "val")
l.Info(ctx, "msg1")
nl := l.Clone(logger.WithLevel(logger.DebugLevel))
nl.Debug(ctx, "msg2")
l.Debug(ctx, "msg3")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"msg1"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"msg2"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if bytes.Contains(buf.Bytes(), []byte(`"msg3"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestMultipleFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l = l.Fields("key", "val")
l = l.Fields("key1", "val1")
l.Info(ctx, "msg")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"key1":"val1"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestError(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
@ -29,13 +112,22 @@ func TestError(t *testing.T) {
func TestErrorf(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithAddStacktrace(true))
if err := l.Init(); err != nil {
if err := l.Init(logger.WithContextAttrFuncs(func(ctx context.Context) []interface{} {
return nil
})); err != nil {
t.Fatal(err)
}
l.Errorf(ctx, "message", fmt.Errorf("error message"))
l.Log(ctx, logger.ErrorLevel, "message", errors.New("error msg"))
l.Log(ctx, logger.ErrorLevel, "", errors.New("error msg"))
if !bytes.Contains(buf.Bytes(), []byte(`"error":"error msg"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
@ -99,6 +191,11 @@ func TestFromContextWithFields(t *testing.T) {
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
l.Info(ctx, "test", "uncorrected number attributes")
if !bytes.Contains(buf.Bytes(), []byte(`"!BADKEY":"uncorrected number attributes"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestClone(t *testing.T) {
@ -174,3 +271,52 @@ func TestLogger(t *testing.T) {
t.Fatalf("logger warn, buf %s", buf.Bytes())
}
}
func Test_WithContextAttrFunc(t *testing.T) {
loggerContextAttrFuncs := []logger.ContextAttrFunc{
func(ctx context.Context) []interface{} {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil
}
attrs := make([]interface{}, 0, 10)
for k, v := range md {
switch k {
case "X-Request-Id", "Phone", "External-Id", "Source-Service", "X-App-Install-Id", "Client-Id", "Client-Ip":
attrs = append(attrs, strings.ToLower(k), v)
}
}
return attrs
},
}
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
ctx := context.TODO()
ctx = metadata.AppendIncomingContext(ctx, "X-Request-Id", uuid.New().String(),
"Source-Service", "Test-System")
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Info(ctx, "test message")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"info"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test message"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"x-request-id":"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
if !(bytes.Contains(buf.Bytes(), []byte(`"source-service":"Test-System"`))) {
t.Fatalf("logger info, buf %s", buf.Bytes())
}
buf.Reset()
imd, _ := metadata.FromIncomingContext(ctx)
l.Info(ctx, "test message1")
imd.Set("Source-Service", "Test-System2")
l.Info(ctx, "test message2")
// t.Logf("xxx %s", buf.Bytes())
}

View File

@ -1,399 +0,0 @@
// Package wrapper provides wrapper for Logger
package wrapper
import (
"context"
"fmt"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/server"
)
var (
// DefaultClientCallObserver called by wrapper in client Call
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultClientStreamObserver called by wrapper in client Stream
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultClientPublishObserver called by wrapper in client Publish
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, err error) []string {
labels := []string{"endpoint", msg.Topic()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultServerHandlerObserver called by wrapper in server Handler
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultServerSubscriberObserver called by wrapper in server Subscriber
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, err error) []string {
labels := []string{"endpoint", msg.Topic()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultClientCallFuncObserver called by wrapper in client CallFunc
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, err error) []string {
labels := []string{"service", req.Service(), "endpoint", req.Endpoint()}
if err != nil {
labels = append(labels, "error", err.Error())
}
return labels
}
// DefaultSkipEndpoints wrapper not called for this endpoints
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
type lWrapper struct {
client.Client
serverHandler server.HandlerFunc
serverSubscriber server.SubscriberFunc
clientCallFunc client.CallFunc
opts Options
}
type (
// ClientCallObserver func signature
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
// ClientStreamObserver func signature
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
// ClientPublishObserver func signature
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
// ClientCallFuncObserver func signature
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
// ServerHandlerObserver func signature
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
// ServerSubscriberObserver func signature
ServerSubscriberObserver func(context.Context, server.Message, error) []string
)
// Options struct for wrapper
type Options struct {
// Logger that used for log
Logger logger.Logger
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// ClientCallObservers funcs
ClientCallObservers []ClientCallObserver
// ClientStreamObservers funcs
ClientStreamObservers []ClientStreamObserver
// ClientPublishObservers funcs
ClientPublishObservers []ClientPublishObserver
// ClientCallFuncObservers funcs
ClientCallFuncObservers []ClientCallFuncObserver
// SkipEndpoints
SkipEndpoints []string
// Level for logger
Level logger.Level
// Enabled flag
Enabled bool
}
// Option func signature
type Option func(*Options)
// NewOptions creates Options from Option slice
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Level: logger.TraceLevel,
ClientCallObservers: []ClientCallObserver{DefaultClientCallObserver},
ClientStreamObservers: []ClientStreamObserver{DefaultClientStreamObserver},
ClientPublishObservers: []ClientPublishObserver{DefaultClientPublishObserver},
ClientCallFuncObservers: []ClientCallFuncObserver{DefaultClientCallFuncObserver},
ServerHandlerObservers: []ServerHandlerObserver{DefaultServerHandlerObserver},
ServerSubscriberObservers: []ServerSubscriberObserver{DefaultServerSubscriberObserver},
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
o(&options)
}
return options
}
// WithEnabled enable/diable flag
func WithEnabled(b bool) Option {
return func(o *Options) {
o.Enabled = b
}
}
// WithLevel log level
func WithLevel(l logger.Level) Option {
return func(o *Options) {
o.Level = l
}
}
// WithLogger logger
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// WithClientCallObservers funcs
func WithClientCallObservers(ob ...ClientCallObserver) Option {
return func(o *Options) {
o.ClientCallObservers = ob
}
}
// WithClientStreamObservers funcs
func WithClientStreamObservers(ob ...ClientStreamObserver) Option {
return func(o *Options) {
o.ClientStreamObservers = ob
}
}
// WithClientPublishObservers funcs
func WithClientPublishObservers(ob ...ClientPublishObserver) Option {
return func(o *Options) {
o.ClientPublishObservers = ob
}
}
// WithClientCallFuncObservers funcs
func WithClientCallFuncObservers(ob ...ClientCallFuncObserver) Option {
return func(o *Options) {
o.ClientCallFuncObservers = ob
}
}
// WithServerHandlerObservers funcs
func WithServerHandlerObservers(ob ...ServerHandlerObserver) Option {
return func(o *Options) {
o.ServerHandlerObservers = ob
}
}
// WithServerSubscriberObservers funcs
func WithServerSubscriberObservers(ob ...ServerSubscriberObserver) Option {
return func(o *Options) {
o.ServerSubscriberObservers = ob
}
}
// SkipEndpoins
func SkipEndpoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
err := l.Client.Call(ctx, req, rsp, opts...)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ClientCallObservers {
labels = append(labels, o(ctx, req, rsp, opts, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
stream, err := l.Client.Stream(ctx, req, opts...)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return stream, err
}
}
if !l.opts.Enabled {
return stream, err
}
var labels []string
for _, o := range l.opts.ClientStreamObservers {
labels = append(labels, o(ctx, req, opts, stream, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return stream, err
}
func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
err := l.Client.Publish(ctx, msg, opts...)
endpoint := msg.Topic()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ClientPublishObservers {
labels = append(labels, o(ctx, msg, opts, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp interface{}) error {
err := l.serverHandler(ctx, req, rsp)
endpoint := req.Endpoint()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ServerHandlerObservers {
labels = append(labels, o(ctx, req, rsp, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
err := l.serverSubscriber(ctx, msg)
endpoint := msg.Topic()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ServerSubscriberObservers {
labels = append(labels, o(ctx, msg, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
// NewClientWrapper accepts an open options and returns a Client Wrapper
func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client {
options := NewOptions()
for _, o := range opts {
o(&options)
}
return &lWrapper{opts: options, Client: c}
}
}
// NewClientCallWrapper accepts an options and returns a Call Wrapper
func NewClientCallWrapper(opts ...Option) client.CallWrapper {
return func(h client.CallFunc) client.CallFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
l := &lWrapper{opts: options, clientCallFunc: h}
return l.ClientCallFunc
}
}
func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
err := l.clientCallFunc(ctx, addr, req, rsp, opts)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
var labels []string
for _, o := range l.opts.ClientCallFuncObservers {
labels = append(labels, o(ctx, addr, req, rsp, opts, err)...)
}
l.opts.Logger.Fields(labels).Log(ctx, l.opts.Level)
return err
}
// NewServerHandlerWrapper accepts an options and returns a Handler Wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
return func(h server.HandlerFunc) server.HandlerFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
l := &lWrapper{opts: options, serverHandler: h}
return l.ServerHandler
}
}
// NewServerSubscriberWrapper accepts an options and returns a Subscriber Wrapper
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
return func(h server.SubscriberFunc) server.SubscriberFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
l := &lWrapper{opts: options, serverSubscriber: h}
return l.ServerSubscriber
}
}

View File

@ -1,5 +1,5 @@
// Package metadata is a way of defining message headers
package metadata // import "go.unistack.org/micro/v3/metadata"
package metadata
import (
"net/textproto"

View File

@ -5,6 +5,28 @@ import (
"testing"
)
func TestMultipleUsage(t *testing.T) {
ctx := context.TODO()
md := New(0)
md.Set("key1_1", "val1_1", "key1_2", "val1_2", "key1_3", "val1_3")
ctx = NewIncomingContext(ctx, Copy(md))
ctx = NewOutgoingContext(ctx, Copy(md))
imd, _ := FromIncomingContext(ctx)
omd, _ := FromOutgoingContext(ctx)
_ = func(x context.Context) context.Context {
m, _ := FromIncomingContext(x)
m.Del("key1_2")
return ctx
}(ctx)
_ = func(x context.Context) context.Context {
m, _ := FromIncomingContext(x)
m.Del("key1_3")
return ctx
}(ctx)
t.Logf("imd %#+v", imd)
t.Logf("omd %#+v", omd)
}
func TestMetadataSetMultiple(t *testing.T) {
md := New(4)
md.Set("key1", "val1", "key2", "val2", "key3")

View File

@ -16,14 +16,19 @@ var (
DefaultAddress = ":9090"
// DefaultPath the meter endpoint where the Meter data will be made available
DefaultPath = "/metrics"
// DefaultMetricPrefix holds the string that prepends to all metrics
DefaultMetricPrefix = "micro_"
// DefaultLabelPrefix holds the string that prepends to all labels
DefaultLabelPrefix = "micro_"
// DefaultMeterStatsInterval specifies interval for meter updating
DefaultMeterStatsInterval = 5 * time.Second
// DefaultSummaryQuantiles is the default spread of stats for summary
DefaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1}
// DefaultSummaryWindow is the default window for summary
DefaultSummaryWindow = 5 * time.Minute
// DefaultSkipEndpoints is the slice of endpoint that must not be metered
DefaultSkipEndpoints = []string{
"MeterService.Metrics",
"HealthService.Live",
"HealthService.Ready",
"HealthService.Version",
}
)
// Meter is an interface for collecting and instrumenting metrics

View File

@ -2,8 +2,6 @@ package meter
import (
"context"
"go.unistack.org/micro/v3/logger"
)
// Option powers the configuration for metrics implementations:
@ -11,8 +9,6 @@ type Option func(*Options)
// Options for metrics implementations
type Options struct {
// Logger used for logging
Logger logger.Logger
// Context holds external options
Context context.Context
// Name holds the meter name
@ -21,10 +17,6 @@ type Options struct {
Address string
// Path holds the path for metrics
Path string
// MetricPrefix holds the prefix for all metrics
MetricPrefix string
// LabelPrefix holds the prefix for all labels
LabelPrefix string
// Labels holds the default labels
Labels []string
// WriteProcessMetrics flag to write process metrics
@ -36,12 +28,9 @@ type Options struct {
// NewOptions prepares a set of options:
func NewOptions(opt ...Option) Options {
opts := Options{
Address: DefaultAddress,
Path: DefaultPath,
Context: context.Background(),
Logger: logger.DefaultLogger,
MetricPrefix: DefaultMetricPrefix,
LabelPrefix: DefaultLabelPrefix,
Address: DefaultAddress,
Path: DefaultPath,
Context: context.Background(),
}
for _, o := range opt {
@ -51,20 +40,6 @@ func NewOptions(opt ...Option) Options {
return opts
}
// LabelPrefix sets the labels prefix
func LabelPrefix(pref string) Option {
return func(o *Options) {
o.LabelPrefix = pref
}
}
// MetricPrefix sets the metric prefix
func MetricPrefix(pref string) Option {
return func(o *Options) {
o.MetricPrefix = pref
}
}
// Context sets the metrics context
func Context(ctx context.Context) Option {
return func(o *Options) {
@ -95,14 +70,7 @@ func TimingObjectives(value map[float64]float64) Option {
}
*/
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Labels sets the meter labels
// Labels add the meter labels
func Labels(ls ...string) Option {
return func(o *Options) {
o.Labels = append(o.Labels, ls...)

View File

@ -1,347 +0,0 @@
package wrapper // import "go.unistack.org/micro/v3/meter/wrapper"
import (
"context"
"fmt"
"time"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/server"
)
var (
// ClientRequestDurationSeconds specifies meter metric name
ClientRequestDurationSeconds = "client_request_duration_seconds"
// ClientRequestLatencyMicroseconds specifies meter metric name
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
// ClientRequestTotal specifies meter metric name
ClientRequestTotal = "client_request_total"
// ClientRequestInflight specifies meter metric name
ClientRequestInflight = "client_request_inflight"
// ServerRequestDurationSeconds specifies meter metric name
ServerRequestDurationSeconds = "server_request_duration_seconds"
// ServerRequestLatencyMicroseconds specifies meter metric name
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
// ServerRequestTotal specifies meter metric name
ServerRequestTotal = "server_request_total"
// ServerRequestInflight specifies meter metric name
ServerRequestInflight = "server_request_inflight"
// PublishMessageDurationSeconds specifies meter metric name
PublishMessageDurationSeconds = "publish_message_duration_seconds"
// PublishMessageLatencyMicroseconds specifies meter metric name
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
// PublishMessageTotal specifies meter metric name
PublishMessageTotal = "publish_message_total"
// PublishMessageInflight specifies meter metric name
PublishMessageInflight = "publish_message_inflight"
// SubscribeMessageDurationSeconds specifies meter metric name
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
// SubscribeMessageLatencyMicroseconds specifies meter metric name
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
// SubscribeMessageTotal specifies meter metric name
SubscribeMessageTotal = "subscribe_message_total"
// SubscribeMessageInflight specifies meter metric name
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success"
labelFailure = "failure"
labelStatus = "status"
labelEndpoint = "endpoint"
// DefaultSkipEndpoints contains list of endpoints that not evaluted by wrapper
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
// Options struct
type Options struct {
Meter meter.Meter
lopts []meter.Option
SkipEndpoints []string
}
// Option func signature
type Option func(*Options)
// NewOptions creates new Options struct
func NewOptions(opts ...Option) Options {
options := Options{
Meter: meter.DefaultMeter,
lopts: make([]meter.Option, 0, 5),
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
o(&options)
}
return options
}
// ServiceName passes service name to meter label
func ServiceName(name string) Option {
return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("name", name))
}
}
// ServiceVersion passes service version to meter label
func ServiceVersion(version string) Option {
return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("version", version))
}
}
// ServiceID passes service id to meter label
func ServiceID(id string) Option {
return func(o *Options) {
o.lopts = append(o.lopts, meter.Labels("id", id))
}
}
// Meter passes meter
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// SkipEndoints add endpoint to skip
func SkipEndoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
type wrapper struct {
client.Client
callFunc client.CallFunc
opts Options
}
// NewClientWrapper create new client wrapper
func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client {
handler := &wrapper{
opts: NewOptions(opts...),
Client: c,
}
return handler
}
}
// NewCallWrapper create new call wrapper
func NewCallWrapper(opts ...Option) client.CallWrapper {
return func(fn client.CallFunc) client.CallFunc {
handler := &wrapper{
opts: NewOptions(opts...),
callFunc: fn,
}
return handler.CallFunc
}
}
func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.callFunc(ctx, addr, req, rsp, opts)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Call(ctx, req, rsp, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Stream(ctx, req, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return stream, err
}
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
return err
}
// NewHandlerWrapper create new server handler wrapper
// deprecated
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
// NewServerHandlerWrapper create new server handler wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Service() + "." + req.Endpoint()
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return fn(ctx, req, rsp)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, req, rsp)
te := time.Since(ts)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
return err
}
}
// NewSubscriberWrapper create server subscribe wrapper
// deprecated
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
handler := &wrapper{
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, msg)
te := time.Since(ts)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
labels = append(labels, labelStatus, labelSuccess)
} else {
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
return err
}
}

View File

@ -66,6 +66,12 @@ type bro struct {
func (p *bro) Name() string { return p.name }
func (p *bro) Live() bool { return true }
func (p *bro) Ready() bool { return true }
func (p *bro) Health() bool { return true }
func (p *bro) Init(opts ...broker.Option) error { return nil }
// Options returns broker options

View File

@ -1,4 +1,4 @@
package mtls // import "go.unistack.org/micro/v3/mtls"
package mtls
import (
"bytes"

View File

@ -1,5 +1,5 @@
// Package network is for creating internetworks
package network // import "go.unistack.org/micro/v3/network"
package network
import (
"go.unistack.org/micro/v3/client"

View File

@ -1,5 +1,5 @@
// Package transport is an interface for synchronous connection based communication
package transport // import "go.unistack.org/micro/v3/network/transport"
package transport
import (
"context"

View File

@ -1,5 +1,5 @@
// Package broker is a tunnel broker
package broker // import "go.unistack.org/micro/v3/network/tunnel/broker"
package broker
import (
"context"
@ -45,6 +45,18 @@ type (
tunnelAddr struct{}
)
func (t *tunBroker) Live() bool {
return true
}
func (t *tunBroker) Ready() bool {
return true
}
func (t *tunBroker) Health() bool {
return true
}
func (t *tunBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&t.opts)
@ -177,12 +189,12 @@ func (t *tunBatchSubscriber) run() {
// receive message
m := new(transport.Message)
if err := c.Recv(m); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
if logger.DefaultLogger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err)
}
if err = c.Close(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
if logger.DefaultLogger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err)
}
}
continue
@ -222,12 +234,12 @@ func (t *tunSubscriber) run() {
// receive message
m := new(transport.Message)
if err := c.Recv(m); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
if logger.DefaultLogger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err)
}
if err = c.Close(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
if logger.DefaultLogger.V(logger.ErrorLevel) {
logger.DefaultLogger.Error(t.opts.Context, err.Error(), err)
}
}
continue
@ -305,6 +317,10 @@ func (t *tunEvent) SetError(err error) {
t.err = err
}
func (t *tunEvent) Context() context.Context {
return context.TODO()
}
// NewBroker returns new tunnel broker
func NewBroker(opts ...broker.Option) (broker.Broker, error) {
options := broker.NewOptions(opts...)

View File

@ -1,5 +1,5 @@
// Package transport provides a tunnel transport
package transport // import "go.unistack.org/micro/v3/network/tunnel/transport"
package transport
import (
"context"

View File

@ -1,5 +1,5 @@
// Package tunnel provides gre network tunnelling
package tunnel // import "go.unistack.org/micro/v3/network/transport/tunnel"
package tunnel
import (
"context"

View File

@ -269,15 +269,7 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
}
}
}
for _, mtr := range o.Meters {
for _, or := range lopts.meters {
if mtr.Name() == or || all {
if err = mtr.Init(meter.Logger(l)); err != nil {
return err
}
}
}
}
for _, trc := range o.Tracers {
for _, ot := range lopts.tracers {
if trc.Name() == ot || all {

View File

@ -1,4 +1,4 @@
package options // import "go.unistack.org/micro/v3/options"
package options
// Hook func interface
type Hook interface{}

View File

@ -1,5 +1,5 @@
// Package http enables the http profiler
package http // import "go.unistack.org/micro/v3/profiler/http"
package http
import (
"context"

View File

@ -1,5 +1,5 @@
// Package pprof provides a pprof profiler which writes output to /tmp/[name].{cpu,mem}.pprof
package pprof // import "go.unistack.org/micro/v3/profiler/pprof"
package pprof
import (
"os"

View File

@ -1,5 +1,5 @@
// Package profiler is for profilers
package profiler // import "go.unistack.org/micro/v3/profiler"
package profiler
// Profiler interface
type Profiler interface {

View File

@ -1,5 +1,5 @@
// Package proxy is a transparent proxy built on the micro/server
package proxy // import "go.unistack.org/micro/v3/proxy"
package proxy
import (
"context"

View File

@ -2,6 +2,7 @@ package register
import (
"context"
"fmt"
"sync"
"time"
@ -64,7 +65,7 @@ func (m *memory) ttlPrune() {
for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.ID, service)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register TTL expired for node %s of service %s", n.ID, service))
}
delete(m.records[domain][service][version].Nodes, id)
}
@ -151,7 +152,7 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
if _, ok := srvs[s.Name][s.Version]; !ok {
srvs[s.Name][s.Version] = r
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register added new service: %s, version: %s", s.Name, s.Version)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register added new service: %s, version: %s", s.Name, s.Version))
}
m.records[options.Domain] = srvs
go m.sendEvent(&register.Result{Action: "create", Service: s})
@ -191,14 +192,14 @@ func (m *memory) Register(ctx context.Context, s *register.Service, opts ...regi
if addedNodes {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register added new node to service: %s, version: %s", s.Name, s.Version)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register added new node to service: %s, version: %s", s.Name, s.Version))
}
go m.sendEvent(&register.Result{Action: "update", Service: s})
} else {
// refresh TTL and timestamp
for _, n := range s.Nodes {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, s.Version)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Updated registration for service: %s, version: %s", s.Name, s.Version))
}
srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
@ -243,7 +244,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
for _, n := range s.Nodes {
if _, ok := version.Nodes[n.ID]; ok {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, s.Version)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed node from service: %s, version: %s", s.Name, s.Version))
}
delete(version.Nodes, n.ID)
}
@ -264,7 +265,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
go m.sendEvent(&register.Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s", s.Name)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s", s.Name))
}
return nil
}
@ -273,7 +274,7 @@ func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...re
delete(m.records[options.Domain][s.Name], s.Version)
go m.sendEvent(&register.Result{Action: "delete", Service: s})
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register removed service: %s, version: %s", s.Name, s.Version)
m.opts.Logger.Debug(m.opts.Context, fmt.Sprintf("Register removed service: %s, version: %s", s.Name, s.Version))
}
return nil

View File

@ -1,5 +1,5 @@
// Package register is an interface for service discovery
package register // import "go.unistack.org/micro/v3/register"
package register
import (
"context"
@ -29,17 +29,32 @@ var (
// and an abstraction over varying implementations
// {consul, etcd, zookeeper, ...}
type Register interface {
// Name returns register name
Name() string
// Init initialize register
Init(...Option) error
// Options returns options for register
Options() Options
// Connect initialize connect to register
Connect(context.Context) error
// Disconnect initialize discconection from register
Disconnect(context.Context) error
// Register service in registry
Register(context.Context, *Service, ...RegisterOption) error
// Deregister service from registry
Deregister(context.Context, *Service, ...DeregisterOption) error
// LookupService in registry
LookupService(context.Context, string, ...LookupOption) ([]*Service, error)
// ListServices in registry
ListServices(context.Context, ...ListOption) ([]*Service, error)
// Watch registry events
Watch(context.Context, ...WatchOption) (Watcher, error)
// String returns registry string representation
String() string
// Live returns register liveness
// Live() bool
// Ready returns register readiness
// Ready() bool
}
// Service holds service register info

View File

@ -1,5 +1,5 @@
// Package dns resolves names to dns records
package dns // import "go.unistack.org/micro/v3/resolver/dns"
package dns
import (
"context"
@ -12,9 +12,9 @@ import (
// Resolver is a DNS network resolve
type Resolver struct {
sync.RWMutex
goresolver *net.Resolver
Address string
mu sync.RWMutex
}
// Resolve tries to resolve endpoint address
@ -39,12 +39,12 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
return []*resolver.Record{rec}, nil
}
r.RLock()
r.mu.RLock()
goresolver := r.goresolver
r.RUnlock()
r.mu.RUnlock()
if goresolver == nil {
r.Lock()
r.mu.Lock()
r.goresolver = &net.Resolver{
Dial: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
d := net.Dialer{
@ -53,7 +53,7 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
return d.DialContext(ctx, "udp", r.Address)
},
}
r.Unlock()
r.mu.Unlock()
}
addrs, err := goresolver.LookupIP(context.TODO(), "ip", host)

View File

@ -1,5 +1,5 @@
// Package dnssrv resolves names to dns srv records
package dnssrv // import "go.unistack.org/micro/v3/resolver/dnssrv"
package dnssrv
import (
"fmt"

View File

@ -1,5 +1,5 @@
// Package http resolves names to network addresses using a http request
package http // import "go.unistack.org/micro/v3/resolver/http"
package http
import (
"encoding/json"

View File

@ -1,5 +1,5 @@
// Package noop is a noop resolver
package noop // import "go.unistack.org/micro/v3/resolver/noop"
package noop
import (
"go.unistack.org/micro/v3/resolver"

View File

@ -1,5 +1,5 @@
// Package register resolves names using the micro register
package register // import "go.unistack.org/micro/v3/resolver/registry"
package register
import (
"context"

View File

@ -1,5 +1,5 @@
// Package static is a static resolver
package static // import "go.unistack.org/micro/v3/resolver/static"
package static
import (
"go.unistack.org/micro/v3/resolver"

View File

@ -1,5 +1,5 @@
// Package router provides a network routing control plane
package router // import "go.unistack.org/micro/v3/router"
package router
import (
"errors"

View File

@ -1,4 +1,4 @@
package random // import "go.unistack.org/micro/v3/selector/random"
package random
import (
"go.unistack.org/micro/v3/selector"

View File

@ -1,4 +1,4 @@
package roundrobin // import "go.unistack.org/micro/v3/selector/roundrobin"
package roundrobin
import (
"go.unistack.org/micro/v3/selector"

View File

@ -1,5 +1,5 @@
// Package selector is for node selection and load balancing
package selector // import "go.unistack.org/micro/v3/selector"
package selector
import (
"errors"

22
semconv/broker.go Normal file
View File

@ -0,0 +1,22 @@
package semconv
var (
// PublishMessageDurationSeconds specifies meter metric name
PublishMessageDurationSeconds = "micro_publish_message_duration_seconds"
// PublishMessageLatencyMicroseconds specifies meter metric name
PublishMessageLatencyMicroseconds = "micro_publish_message_latency_microseconds"
// PublishMessageTotal specifies meter metric name
PublishMessageTotal = "micro_publish_message_total"
// PublishMessageInflight specifies meter metric name
PublishMessageInflight = "micro_publish_message_inflight"
// SubscribeMessageDurationSeconds specifies meter metric name
SubscribeMessageDurationSeconds = "micro_subscribe_message_duration_seconds"
// SubscribeMessageLatencyMicroseconds specifies meter metric name
SubscribeMessageLatencyMicroseconds = "micro_subscribe_message_latency_microseconds"
// SubscribeMessageTotal specifies meter metric name
SubscribeMessageTotal = "micro_subscribe_message_total"
// SubscribeMessageInflight specifies meter metric name
SubscribeMessageInflight = "micro_subscribe_message_inflight"
// BrokerGroupLag specifies broker lag
BrokerGroupLag = "micro_broker_group_lag"
)

12
semconv/client.go Normal file
View File

@ -0,0 +1,12 @@
package semconv
var (
// ClientRequestDurationSeconds specifies meter metric name
ClientRequestDurationSeconds = "micro_client_request_duration_seconds"
// ClientRequestLatencyMicroseconds specifies meter metric name
ClientRequestLatencyMicroseconds = "micro_client_request_latency_microseconds"
// ClientRequestTotal specifies meter metric name
ClientRequestTotal = "micro_client_request_total"
// ClientRequestInflight specifies meter metric name
ClientRequestInflight = "micro_client_request_inflight"
)

4
semconv/logger.go Normal file
View File

@ -0,0 +1,4 @@
package semconv
// LoggerMessageTotal specifies meter metric name for logger messages
var LoggerMessageTotal = "micro_logger_message_total"

12
semconv/pool.go Normal file
View File

@ -0,0 +1,12 @@
package semconv
var (
// PoolGetTotal specifies meter metric name for total number of pool get ops
PoolGetTotal = "micro_pool_get_total"
// PoolPutTotal specifies meter metric name for total number of pool put ops
PoolPutTotal = "micro_pool_put_total"
// PoolMisTotal specifies meter metric name for total number of pool misses
PoolMisTotal = "micro_pool_mis_total"
// PoolRetTotal specifies meter metric name for total number of pool returned to gc
PoolRetTotal = "micro_pool_ret_total"
)

12
semconv/server.go Normal file
View File

@ -0,0 +1,12 @@
package semconv
var (
// ServerRequestDurationSeconds specifies meter metric name
ServerRequestDurationSeconds = "micro_server_request_duration_seconds"
// ServerRequestLatencyMicroseconds specifies meter metric name
ServerRequestLatencyMicroseconds = "micro_server_request_latency_microseconds"
// ServerRequestTotal specifies meter metric name
ServerRequestTotal = "micro_server_request_total"
// ServerRequestInflight specifies meter metric name
ServerRequestInflight = "micro_server_request_inflight"
)

12
semconv/store.go Normal file
View File

@ -0,0 +1,12 @@
package semconv
var (
// StoreRequestDurationSeconds specifies meter metric name
StoreRequestDurationSeconds = "micro_store_request_duration_seconds"
// ClientRequestLatencyMicroseconds specifies meter metric name
StoreRequestLatencyMicroseconds = "micro_store_request_latency_microseconds"
// StoreRequestTotal specifies meter metric name
StoreRequestTotal = "micro_store_request_total"
// StoreRequestInflight specifies meter metric name
StoreRequestInflight = "micro_store_request_inflight"
)

View File

@ -1,59 +0,0 @@
package server
import (
"reflect"
"go.unistack.org/micro/v3/register"
)
type rpcHandler struct {
opts HandlerOptions
handler interface{}
name string
endpoints []*register.Endpoint
}
func newRPCHandler(handler interface{}, opts ...HandlerOption) Handler {
options := NewHandlerOptions(opts...)
typ := reflect.TypeOf(handler)
hdlr := reflect.ValueOf(handler)
name := reflect.Indirect(hdlr).Type().Name()
var endpoints []*register.Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := register.ExtractEndpoint(typ.Method(m)); e != nil {
e.Name = name + "." + e.Name
for k, v := range options.Metadata[e.Name] {
e.Metadata[k] = v
}
endpoints = append(endpoints, e)
}
}
return &rpcHandler{
name: name,
handler: handler,
endpoints: endpoints,
opts: options,
}
}
func (r *rpcHandler) Name() string {
return r.name
}
func (r *rpcHandler) Handler() interface{} {
return r.handler
}
func (r *rpcHandler) Endpoints() []*register.Endpoint {
return r.endpoints
}
func (r *rpcHandler) Options() HandlerOptions {
return r.opts
}

View File

@ -1,14 +1,21 @@
package server
import (
"context"
"fmt"
"reflect"
"runtime/debug"
"sort"
"strings"
"sync"
"time"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
maddr "go.unistack.org/micro/v3/util/addr"
mnet "go.unistack.org/micro/v3/util/net"
@ -24,6 +31,58 @@ const (
defaultContentType = "application/json"
)
type rpcHandler struct {
opts HandlerOptions
handler interface{}
name string
endpoints []*register.Endpoint
}
func newRPCHandler(handler interface{}, opts ...HandlerOption) Handler {
options := NewHandlerOptions(opts...)
typ := reflect.TypeOf(handler)
hdlr := reflect.ValueOf(handler)
name := reflect.Indirect(hdlr).Type().Name()
var endpoints []*register.Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := register.ExtractEndpoint(typ.Method(m)); e != nil {
e.Name = name + "." + e.Name
for k, v := range options.Metadata[e.Name] {
e.Metadata[k] = v
}
endpoints = append(endpoints, e)
}
}
return &rpcHandler{
name: name,
handler: handler,
endpoints: endpoints,
opts: options,
}
}
func (r *rpcHandler) Name() string {
return r.name
}
func (r *rpcHandler) Handler() interface{} {
return r.handler
}
func (r *rpcHandler) Endpoints() []*register.Endpoint {
return r.endpoints
}
func (r *rpcHandler) Options() HandlerOptions {
return r.opts
}
type noopServer struct {
h Handler
wg *sync.WaitGroup
@ -62,6 +121,18 @@ func (n *noopServer) newCodec(contentType string) (codec.Codec, error) {
return nil, codec.ErrUnknownContentType
}
func (n *noopServer) Live() bool {
return true
}
func (n *noopServer) Ready() bool {
return true
}
func (n *noopServer) Health() bool {
return true
}
func (n *noopServer) Handle(handler Handler) error {
n.h = handler
return nil
@ -94,6 +165,35 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
return nil
}
type rpcMessage struct {
payload interface{}
codec codec.Codec
header metadata.Metadata
topic string
contentType string
body []byte
}
func (r *rpcMessage) ContentType() string {
return r.contentType
}
func (r *rpcMessage) Topic() string {
return r.topic
}
func (r *rpcMessage) Body() interface{} {
return r.payload
}
func (r *rpcMessage) Header() metadata.Metadata {
return r.header
}
func (r *rpcMessage) Codec() codec.Codec {
return r.codec
}
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
return newRPCHandler(h, opts...)
}
@ -185,7 +285,7 @@ func (n *noopServer) Register() error {
if !registered {
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID)
config.Logger.Info(n.opts.Context, fmt.Sprintf("register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID))
}
}
@ -223,7 +323,7 @@ func (n *noopServer) Deregister() error {
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].ID)
config.Logger.Info(n.opts.Context, fmt.Sprintf("deregistering node: %s", service.Nodes[0].ID))
}
if err := DefaultDeregisterFunc(service, config); err != nil {
@ -254,11 +354,11 @@ func (n *noopServer) Deregister() error {
go func(s broker.Subscriber) {
defer wg.Done()
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "unsubscribing from topic: %s", s.Topic())
config.Logger.Info(n.opts.Context, "unsubscribing from topic: "+s.Topic())
}
if err := s.Unsubscribe(ncx); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "unsubscribing from topic: %s err: %v", s.Topic(), err)
config.Logger.Error(n.opts.Context, "unsubscribing from topic: "+s.Topic(), err)
}
}
}(subs[idx])
@ -294,7 +394,7 @@ func (n *noopServer) Start() error {
config.Address = addr
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "server [noop] Listening on %s", config.Address)
config.Logger.Info(n.opts.Context, "server [noop] Listening on "+config.Address)
}
n.Lock()
@ -308,13 +408,13 @@ func (n *noopServer) Start() error {
// connect to the broker
if err := config.Broker.Connect(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "broker [%s] connect error: %v", config.Broker.String(), err)
config.Logger.Error(n.opts.Context, fmt.Sprintf("broker [%s] connect error", config.Broker.String()), err)
}
return err
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "broker [%s] Connected to %s", config.Broker.String(), config.Broker.Address())
config.Logger.Info(n.opts.Context, fmt.Sprintf("broker [%s] Connected to %s", config.Broker.String(), config.Broker.Address()))
}
}
@ -322,13 +422,13 @@ func (n *noopServer) Start() error {
// nolint: nestif
if err := config.RegisterCheck(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, err)
config.Logger.Error(n.opts.Context, fmt.Sprintf("server %s-%s register check error", config.Name, config.ID), err)
}
} else {
// announce self to the world
if err := n.Register(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server register error: %v", err)
config.Logger.Error(n.opts.Context, "server register error", err)
}
}
}
@ -361,23 +461,23 @@ func (n *noopServer) Start() error {
// nolint: nestif
if rerr != nil && registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr)
config.Logger.Error(n.opts.Context, fmt.Sprintf("server %s-%s register check error, deregister it", config.Name, config.ID), rerr)
}
// deregister self in case of error
if err := n.Deregister(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.ID, err)
config.Logger.Error(n.opts.Context, fmt.Sprintf("server %s-%s deregister error", config.Name, config.ID), err)
}
}
} else if rerr != nil && !registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, rerr)
config.Logger.Error(n.opts.Context, fmt.Sprintf("server %s-%s register check error", config.Name, config.ID), rerr)
}
continue
}
if err := n.Register(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.ID, err)
config.Logger.Error(n.opts.Context, fmt.Sprintf("server %s-%s register error", config.Name, config.ID), err)
}
}
// wait for exit
@ -389,7 +489,7 @@ func (n *noopServer) Start() error {
// deregister self
if err := n.Deregister(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server deregister error: ", err)
config.Logger.Error(n.opts.Context, "server deregister error", err)
}
}
@ -402,12 +502,12 @@ func (n *noopServer) Start() error {
ch <- nil
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "broker [%s] Disconnected from %s", config.Broker.String(), config.Broker.Address())
config.Logger.Info(n.opts.Context, fmt.Sprintf("broker [%s] Disconnected from %s", config.Broker.String(), config.Broker.Address()))
}
// disconnect broker
if err := config.Broker.Disconnect(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "broker [%s] disconnect error: %v", config.Broker.String(), err)
config.Logger.Error(n.opts.Context, fmt.Sprintf("broker [%s] disconnect error", config.Broker.String()), err)
}
}
}()
@ -423,36 +523,33 @@ func (n *noopServer) Start() error {
func (n *noopServer) subscribe() error {
config := n.Options()
cx := config.Context
var err error
var sub broker.Subscriber
subCtx := config.Context
for sb := range n.subscribers {
if sb.Options().Context != nil {
cx = sb.Options().Context
if cx := sb.Options().Context; cx != nil {
subCtx = cx
}
opts := []broker.SubscribeOption{
broker.SubscribeContext(subCtx),
broker.SubscribeAutoAck(sb.Options().AutoAck),
broker.SubscribeBodyOnly(sb.Options().BodyOnly),
}
opts := []broker.SubscribeOption{broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck)}
if queue := sb.Options().Queue; len(queue) > 0 {
opts = append(opts, broker.SubscribeGroup(queue))
}
if sb.Options().Batch {
// batch processing handler
sub, err = config.Broker.BatchSubscribe(cx, sb.Topic(), n.createBatchSubHandler(sb, config), opts...)
} else {
// single processing handler
sub, err = config.Broker.Subscribe(cx, sb.Topic(), n.createSubHandler(sb, config), opts...)
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info(n.opts.Context, "subscribing to topic: "+sb.Topic())
}
sub, err := config.Broker.Subscribe(subCtx, sb.Topic(), n.createSubHandler(sb, config), opts...)
if err != nil {
return err
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "subscribing to topic: %s", sb.Topic())
}
n.subscribers[sb] = []broker.Subscriber{sub}
}
@ -478,3 +575,218 @@ func (n *noopServer) Stop() error {
return err
}
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
var endpoints []*register.Endpoint
var handlers []*handler
options := NewSubscriberOptions(opts...)
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
h := &handler{
method: reflect.ValueOf(sub),
}
switch typ.NumIn() {
case 1:
h.reqType = typ.In(0)
case 2:
h.ctxType = typ.In(0)
h.reqType = typ.In(1)
}
handlers = append(handlers, h)
ep := &register.Endpoint{
Name: "Func",
Request: register.ExtractSubValue(typ),
Metadata: metadata.New(2),
}
ep.Metadata.Set("topic", topic)
ep.Metadata.Set("subscriber", "true")
endpoints = append(endpoints, ep)
} else {
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
h := &handler{
method: method.Func,
}
switch method.Type.NumIn() {
case 2:
h.reqType = method.Type.In(1)
case 3:
h.ctxType = method.Type.In(1)
h.reqType = method.Type.In(2)
}
handlers = append(handlers, h)
ep := &register.Endpoint{
Name: name + "." + method.Name,
Request: register.ExtractSubValue(method.Type),
Metadata: metadata.New(2),
}
ep.Metadata.Set("topic", topic)
ep.Metadata.Set("subscriber", "true")
endpoints = append(endpoints, ep)
}
}
return &subscriber{
rcvr: reflect.ValueOf(sub),
typ: reflect.TypeOf(sub),
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}
//nolint:gocyclo
func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
return func(p broker.Event) (err error) {
defer func() {
if r := recover(); r != nil {
n.RLock()
config := n.opts
n.RUnlock()
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(n.opts.Context, "panic recovered: ", r)
config.Logger.Error(n.opts.Context, string(debug.Stack()))
}
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
}
}()
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = metadata.New(2)
}
ct := msg.Header["Content-Type"]
if len(ct) == 0 {
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
ct = defaultContentType
}
cf, err := n.newCodec(ct)
if err != nil {
return err
}
hdr := metadata.New(len(msg.Header))
for k, v := range msg.Header {
hdr.Set(k, v)
}
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var isVal bool
var req reflect.Value
if handler.reqType.Kind() == reflect.Ptr {
req = reflect.New(handler.reqType.Elem())
} else {
req = reflect.New(handler.reqType)
isVal = true
}
if isVal {
req = req.Elem()
}
if err = cf.Unmarshal(msg.Body, req.Interface()); err != nil {
return err
}
fn := func(ctx context.Context, msg Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctx))
}
vals = append(vals, reflect.ValueOf(msg.Body()))
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
opts.Hooks.EachNext(func(hook options.Hook) {
if h, ok := hook.(HookSubHandler); ok {
fn = h(fn)
}
})
if n.wg != nil {
n.wg.Add(1)
}
go func() {
if n.wg != nil {
defer n.wg.Done()
}
cerr := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
})
results <- cerr
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
func (s *subscriber) Topic() string {
return s.topic
}
func (s *subscriber) Subscriber() interface{} {
return s.subscriber
}
func (s *subscriber) Endpoints() []*register.Endpoint {
return s.endpoints
}
func (s *subscriber) Options() SubscriberOptions {
return s.opts
}
type subscriber struct {
typ reflect.Type
subscriber interface{}
topic string
endpoints []*register.Endpoint
handlers []*handler
opts SubscriberOptions
rcvr reflect.Value
}
type handler struct {
reqType reflect.Type
ctxType reflect.Type
method reflect.Value
}

View File

@ -9,7 +9,6 @@ import (
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server"
)
@ -26,18 +25,6 @@ func (h *TestHandler) SingleSubHandler(ctx context.Context, msg *codec.Frame) er
return nil
}
func (h *TestHandler) BatchSubHandler(ctxs []context.Context, msgs []*codec.Frame) error {
if len(msgs) != 8 {
h.t.Fatal("invalid number of messages received")
}
for idx := 0; idx < len(msgs); idx++ {
md, _ := metadata.FromIncomingContext(ctxs[idx])
_ = md
// fmt.Printf("msg md %v\n", md)
}
return nil
}
func TestNoopSub(t *testing.T) {
ctx := context.Background()
@ -76,13 +63,6 @@ func TestNoopSub(t *testing.T) {
t.Fatal(err)
}
if err := s.Subscribe(s.NewSubscriber("batch_topic", h.BatchSubHandler,
server.SubscriberQueue("queue"),
server.SubscriberBatch(true),
)); err != nil {
t.Fatal(err)
}
if err := s.Start(); err != nil {
t.Fatal(err)
}

View File

@ -12,7 +12,6 @@ import (
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
msync "go.unistack.org/micro/v3/sync"
@ -37,8 +36,6 @@ type Options struct {
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Transport holds the transport
Transport transport.Transport
/*
// Router for requests
@ -69,12 +66,6 @@ type Options struct {
Advertise string
// Version holds the server version
Version string
// SubWrappers holds the server subscribe wrappers
SubWrappers []SubscriberWrapper
// BatchSubWrappers holds the server batch subscribe wrappers
BatchSubWrappers []BatchSubscriberWrapper
// HdlrWrappers holds the handler wrappers
HdlrWrappers []HandlerWrapper
// RegisterAttempts holds the number of register attempts before error
RegisterAttempts int
// RegisterInterval holds he interval for re-register
@ -85,7 +76,8 @@ type Options struct {
MaxConn int
// DeregisterAttempts holds the number of deregister attempts before error
DeregisterAttempts int
// Hooks may contains SubscriberWrapper, HandlerWrapper or Server func wrapper
// Hooks may contains hook actions that performs before/after server handler
// or server subscriber handler
Hooks options.Hooks
// GracefulTimeout timeout for graceful stop server
GracefulTimeout time.Duration
@ -105,7 +97,6 @@ func NewOptions(opts ...Option) Options {
Tracer: tracer.DefaultTracer,
Broker: broker.DefaultBroker,
Register: register.DefaultRegister,
Transport: transport.DefaultTransport,
Address: DefaultAddress,
Name: DefaultName,
Version: DefaultVersion,
@ -214,13 +205,6 @@ func Tracer(t tracer.Tracer) Option {
}
}
// Transport mechanism for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Metadata associated with the server
func Metadata(md metadata.Metadata) Option {
return func(o *Options) {
@ -254,14 +238,6 @@ func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
_ = o.Transport.Init(
transport.TLSConfig(t),
)
}
}
@ -287,27 +263,6 @@ func Wait(wg *sync.WaitGroup) Option {
}
}
// WrapHandler adds a handler Wrapper to a list of options passed into the server
func WrapHandler(w HandlerWrapper) Option {
return func(o *Options) {
o.HdlrWrappers = append(o.HdlrWrappers, w)
}
}
// WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server
func WrapSubscriber(w SubscriberWrapper) Option {
return func(o *Options) {
o.SubWrappers = append(o.SubWrappers, w)
}
}
// WrapBatchSubscriber adds a batch subscriber Wrapper to a list of options passed into the server
func WrapBatchSubscriber(w BatchSubscriberWrapper) Option {
return func(o *Options) {
o.BatchSubWrappers = append(o.BatchSubWrappers, w)
}
}
// MaxConn specifies maximum number of max simultaneous connections to server
func MaxConn(n int) Option {
return func(o *Options) {
@ -367,8 +322,6 @@ type SubscriberOptions struct {
AutoAck bool
// BodyOnly flag specifies that message without headers
BodyOnly bool
// Batch flag specifies that message processed in batches
Batch bool
// BatchSize flag specifies max size of batch
BatchSize int
// BatchWait flag specifies max wait time for batch filling
@ -440,13 +393,6 @@ func SubscriberAck(b bool) SubscriberOption {
}
}
// SubscriberBatch control batch processing for handler
func SubscriberBatch(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Batch = b
}
}
// SubscriberBatchSize control batch filling size for handler
// Batch filling max waiting time controlled by SubscriberBatchWait
func SubscriberBatchSize(n int) SubscriberOption {
@ -461,3 +407,10 @@ func SubscriberBatchWait(td time.Duration) SubscriberOption {
o.BatchWait = td
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}

View File

@ -1,35 +0,0 @@
package server
import (
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/metadata"
)
type rpcMessage struct {
payload interface{}
codec codec.Codec
header metadata.Metadata
topic string
contentType string
body []byte
}
func (r *rpcMessage) ContentType() string {
return r.contentType
}
func (r *rpcMessage) Topic() string {
return r.topic
}
func (r *rpcMessage) Body() interface{} {
return r.payload
}
func (r *rpcMessage) Header() metadata.Metadata {
return r.header
}
func (r *rpcMessage) Codec() codec.Codec {
return r.codec
}

View File

@ -1,5 +1,5 @@
// Package server is an interface for a micro server
package server // import "go.unistack.org/micro/v3/server"
package server
import (
"context"
@ -11,7 +11,9 @@ import (
)
// DefaultServer default server
var DefaultServer Server = NewServer()
var (
DefaultServer Server = NewServer()
)
var (
// DefaultAddress will be used if no address passed, use secure localhost
@ -60,8 +62,21 @@ type Server interface {
Stop() error
// Server implementation
String() string
// Live returns server liveness
Live() bool
// Ready returns server readiness
Ready() bool
// Health returns server health
Health() bool
}
type (
FuncSubHandler func(ctx context.Context, ms Message) error
HookSubHandler func(next FuncSubHandler) FuncSubHandler
FuncHandler func(ctx context.Context, req Request, rsp interface{}) error
HookHandler func(next FuncHandler) FuncHandler
)
/*
// Router handle serving messages
type Router interface {
@ -147,12 +162,11 @@ type Stream interface {
//
// Example:
//
// type Greeter struct {}
//
// func (g *Greeter) Hello(context, request, response) error {
// return nil
// }
// type Greeter struct {}
//
// func (g *Greeter) Hello(context, request, response) error {
// return nil
// }
type Handler interface {
Name() string
Handler() interface{}

View File

@ -1,52 +1,24 @@
package server
import (
"bytes"
"context"
"fmt"
"reflect"
"runtime/debug"
"strings"
"unicode"
"unicode/utf8"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/errors"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/register"
)
const (
subSig = "func(context.Context, interface{}) error"
batchSubSig = "func([]context.Context, []interface{}) error"
subSig = "func(context.Context, interface{}) error"
)
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
type handler struct {
reqType reflect.Type
ctxType reflect.Type
method reflect.Value
}
type subscriber struct {
typ reflect.Type
subscriber interface{}
topic string
endpoints []*register.Endpoint
handlers []*handler
opts SubscriberOptions
rcvr reflect.Value
}
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
r, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(r)
}
// Is this type exported or a builtin?
@ -69,23 +41,15 @@ func ValidateSubscriber(sub Subscriber) error {
switch typ.NumIn() {
case 2:
argType = typ.In(1)
if sub.Options().Batch {
if argType.Kind() != reflect.Slice {
return fmt.Errorf("subscriber %v dont have required signature %s", name, batchSubSig)
}
if strings.Compare(fmt.Sprintf("%v", argType), "[]interface{}") == 0 {
return fmt.Errorf("subscriber %v dont have required signaure %s", name, batchSubSig)
}
}
default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s or %s", name, typ.NumIn(), subSig, batchSubSig)
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
}
if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s or %s",
name, typ.NumOut(), subSig, batchSubSig)
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s",
name, typ.NumOut(), subSig)
}
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
@ -100,8 +64,8 @@ func ValidateSubscriber(sub Subscriber) error {
case 3:
argType = method.Type.In(2)
default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s or %s",
name, method.Name, method.Type.NumIn(), subSig, batchSubSig)
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
name, method.Name, method.Type.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
@ -109,8 +73,8 @@ func ValidateSubscriber(sub Subscriber) error {
}
if method.Type.NumOut() != 1 {
return fmt.Errorf(
"subscriber %v.%v has wrong number of return values: %v require signature %s or %s",
name, method.Name, method.Type.NumOut(), subSig, batchSubSig)
"subscriber %v.%v has wrong number of return values: %v require signature %s",
name, method.Name, method.Type.NumOut(), subSig)
}
if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
@ -120,318 +84,3 @@ func ValidateSubscriber(sub Subscriber) error {
return nil
}
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
var endpoints []*register.Endpoint
var handlers []*handler
options := NewSubscriberOptions(opts...)
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
h := &handler{
method: reflect.ValueOf(sub),
}
switch typ.NumIn() {
case 1:
h.reqType = typ.In(0)
case 2:
h.ctxType = typ.In(0)
h.reqType = typ.In(1)
}
handlers = append(handlers, h)
ep := &register.Endpoint{
Name: "Func",
Request: register.ExtractSubValue(typ),
Metadata: metadata.New(2),
}
ep.Metadata.Set("topic", topic)
ep.Metadata.Set("subscriber", "true")
endpoints = append(endpoints, ep)
} else {
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
h := &handler{
method: method.Func,
}
switch method.Type.NumIn() {
case 2:
h.reqType = method.Type.In(1)
case 3:
h.ctxType = method.Type.In(1)
h.reqType = method.Type.In(2)
}
handlers = append(handlers, h)
ep := &register.Endpoint{
Name: name + "." + method.Name,
Request: register.ExtractSubValue(method.Type),
Metadata: metadata.New(2),
}
ep.Metadata.Set("topic", topic)
ep.Metadata.Set("subscriber", "true")
endpoints = append(endpoints, ep)
}
}
return &subscriber{
rcvr: reflect.ValueOf(sub),
typ: reflect.TypeOf(sub),
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}
//nolint:gocyclo
func (n *noopServer) createBatchSubHandler(sb *subscriber, opts Options) broker.BatchHandler {
return func(ps broker.Events) (err error) {
defer func() {
if r := recover(); r != nil {
n.RLock()
config := n.opts
n.RUnlock()
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(n.opts.Context, "panic recovered: ", r)
config.Logger.Error(n.opts.Context, string(debug.Stack()))
}
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
}
}()
msgs := make([]Message, 0, len(ps))
ctxs := make([]context.Context, 0, len(ps))
for _, p := range ps {
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = metadata.New(2)
}
ct, _ := msg.Header.Get(metadata.HeaderContentType)
if len(ct) == 0 {
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
ct = defaultContentType
}
hdr := metadata.Copy(msg.Header)
topic, _ := msg.Header.Get(metadata.HeaderTopic)
ctxs = append(ctxs, metadata.NewIncomingContext(sb.opts.Context, hdr))
msgs = append(msgs, &rpcMessage{
topic: topic,
contentType: ct,
header: msg.Header,
body: msg.Body,
})
}
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var req reflect.Value
switch handler.reqType.Kind() {
case reflect.Ptr:
req = reflect.New(handler.reqType.Elem())
default:
req = reflect.New(handler.reqType.Elem()).Elem()
}
reqType := handler.reqType
var cf codec.Codec
for _, msg := range msgs {
cf, err = n.newCodec(msg.ContentType())
if err != nil {
return err
}
rb := reflect.New(req.Type().Elem())
if err = cf.ReadBody(bytes.NewReader(msg.(*rpcMessage).body), rb.Interface()); err != nil {
return err
}
msg.(*rpcMessage).codec = cf
msg.(*rpcMessage).payload = rb.Interface()
}
fn := func(ctxs []context.Context, ms []Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctxs))
}
payloads := reflect.MakeSlice(reqType, 0, len(ms))
for _, m := range ms {
payloads = reflect.Append(payloads, reflect.ValueOf(m.Body()))
}
vals = append(vals, payloads)
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
for i := len(opts.BatchSubWrappers); i > 0; i-- {
fn = opts.BatchSubWrappers[i-1](fn)
}
if n.wg != nil {
n.wg.Add(1)
}
go func() {
if n.wg != nil {
defer n.wg.Done()
}
results <- fn(ctxs, msgs)
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
//nolint:gocyclo
func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
return func(p broker.Event) (err error) {
defer func() {
if r := recover(); r != nil {
n.RLock()
config := n.opts
n.RUnlock()
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(n.opts.Context, "panic recovered: ", r)
config.Logger.Error(n.opts.Context, string(debug.Stack()))
}
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
}
}()
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = metadata.New(2)
}
ct := msg.Header["Content-Type"]
if len(ct) == 0 {
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
ct = defaultContentType
}
cf, err := n.newCodec(ct)
if err != nil {
return err
}
hdr := metadata.New(len(msg.Header))
for k, v := range msg.Header {
hdr.Set(k, v)
}
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var isVal bool
var req reflect.Value
if handler.reqType.Kind() == reflect.Ptr {
req = reflect.New(handler.reqType.Elem())
} else {
req = reflect.New(handler.reqType)
isVal = true
}
if isVal {
req = req.Elem()
}
if err = cf.ReadBody(bytes.NewBuffer(msg.Body), req.Interface()); err != nil {
return err
}
fn := func(ctx context.Context, msg Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctx))
}
vals = append(vals, reflect.ValueOf(msg.Body()))
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
for i := len(opts.SubWrappers); i > 0; i-- {
fn = opts.SubWrappers[i-1](fn)
}
if n.wg != nil {
n.wg.Add(1)
}
go func() {
if n.wg != nil {
defer n.wg.Done()
}
cerr := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
})
results <- cerr
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
func (s *subscriber) Topic() string {
return s.topic
}
func (s *subscriber) Subscriber() interface{} {
return s.subscriber
}
func (s *subscriber) Endpoints() []*register.Endpoint {
return s.endpoints
}
func (s *subscriber) Options() SubscriberOptions {
return s.opts
}

View File

@ -14,20 +14,12 @@ type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
// publication message.
type SubscriberFunc func(ctx context.Context, msg Message) error
// BatchSubscriberFunc represents a single method of a subscriber. It's used primarily
// for the wrappers. What's handed to the actual method is the concrete
// publication message. This func used by batch subscribers
type BatchSubscriberFunc func(ctxs []context.Context, msgs []Message) error
// HandlerWrapper wraps the HandlerFunc and returns the equivalent
type HandlerWrapper func(HandlerFunc) HandlerFunc
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
// BatchSubscriberWrapper wraps the SubscriberFunc and returns the equivalent
type BatchSubscriberWrapper func(BatchSubscriberFunc) BatchSubscriberFunc
// StreamWrapper wraps a Stream interface and returns the equivalent.
// Because streams exist for the lifetime of a method invocation this
// is a convenient way to wrap a Stream as its in use for trace, monitoring,

View File

@ -1,10 +1,14 @@
// Package micro is a pluggable framework for microservices
package micro // import "go.unistack.org/micro/v3"
package micro
import (
"fmt"
"net"
"sync"
"time"
"github.com/KimMachineGun/automemlimit/memlimit"
"go.uber.org/automaxprocs/maxprocs"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/config"
@ -15,8 +19,24 @@ import (
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/tracer"
utildns "go.unistack.org/micro/v3/util/dns"
)
func init() {
maxprocs.Set()
memlimit.SetGoMemLimitWithOpts(
memlimit.WithRatio(0.9),
memlimit.WithProvider(
memlimit.ApplyFallback(
memlimit.FromCgroup,
memlimit.FromSystem,
),
),
)
net.DefaultResolver = utildns.NewNetResolver(utildns.Timeout(1 * time.Second))
}
// Service is an interface that wraps the lower level components.
// Its works as container with building blocks for service.
type Service interface {
@ -57,8 +77,14 @@ type Service interface {
Start() error
// Stop the service
Stop() error
// The service implementation
// String service representation
String() string
// Live returns service liveness
Live() bool
// Ready returns service readiness
Ready() bool
// Health returns service health
Health() bool
}
// RegisterHandler is syntactic sugar for registering a handler
@ -72,22 +98,21 @@ func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...se
}
type service struct {
done chan struct{}
opts Options
sync.RWMutex
}
// NewService creates and returns a new Service based on the packages within.
func NewService(opts ...Option) Service {
return &service{opts: NewOptions(opts...)}
return &service{opts: NewOptions(opts...), done: make(chan struct{})}
}
func (s *service) Name() string {
return s.opts.Name
}
// Init initialises options. Additionally it calls cmd.Init
// which parses command line flags. cmd.Init is only called
// on first Init.
// Init initialises options.
//
//nolint:gocyclo
func (s *service) Init(opts ...Option) error {
@ -236,6 +261,63 @@ func (s *service) String() string {
return s.opts.Name
}
func (s *service) Live() bool {
for _, v := range s.opts.Brokers {
if !v.Live() {
return false
}
}
for _, v := range s.opts.Servers {
if !v.Live() {
return false
}
}
for _, v := range s.opts.Stores {
if !v.Live() {
return false
}
}
return true
}
func (s *service) Ready() bool {
for _, v := range s.opts.Brokers {
if !v.Ready() {
return false
}
}
for _, v := range s.opts.Servers {
if !v.Ready() {
return false
}
}
for _, v := range s.opts.Stores {
if !v.Ready() {
return false
}
}
return true
}
func (s *service) Health() bool {
for _, v := range s.opts.Brokers {
if !v.Health() {
return false
}
}
for _, v := range s.opts.Servers {
if !v.Health() {
return false
}
}
for _, v := range s.opts.Stores {
if !v.Health() {
return false
}
}
return true
}
//nolint:gocyclo
func (s *service) Start() error {
var err error
@ -262,11 +344,7 @@ func (s *service) Start() error {
}
if config.Loggers[0].V(logger.InfoLevel) {
config.Loggers[0].Infof(s.opts.Context, "starting [service] %s version %s", s.Options().Name, s.Options().Version)
}
if len(s.opts.Servers) == 0 {
return fmt.Errorf("cant start nil server")
config.Loggers[0].Info(s.opts.Context, fmt.Sprintf("starting [service] %s version %s", s.Options().Name, s.Options().Version))
}
for _, reg := range s.opts.Registers {
@ -308,7 +386,7 @@ func (s *service) Stop() error {
s.RUnlock()
if config.Loggers[0].V(logger.InfoLevel) {
config.Loggers[0].Infof(s.opts.Context, "stoppping [service] %s", s.Name())
config.Loggers[0].Info(s.opts.Context, fmt.Sprintf("stoppping [service] %s", s.Name()))
}
var err error
@ -348,6 +426,8 @@ func (s *service) Stop() error {
}
}
close(s.done)
return nil
}
@ -371,7 +451,7 @@ func (s *service) Run() error {
}
// wait on context cancel
<-s.opts.Context.Done()
<-s.done
return s.Stop()
}

View File

@ -134,7 +134,7 @@ func TestNewService(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewService(tt.args.opts...); !reflect.DeepEqual(got, tt.want) {
if got := NewService(tt.args.opts...); got.Name() != tt.want.Name() {
t.Errorf("NewService() = %v, want %v", got.Options().Name, tt.want.Options().Name)
}
})

View File

@ -1,197 +0,0 @@
package store
import (
"context"
"sort"
"strings"
"time"
"github.com/patrickmn/go-cache"
)
// NewStore returns a memory store
func NewStore(opts ...Option) Store {
return &memoryStore{
opts: NewOptions(opts...),
store: cache.New(cache.NoExpiration, 5*time.Minute),
}
}
func (m *memoryStore) Connect(ctx context.Context) error {
return nil
}
func (m *memoryStore) Disconnect(ctx context.Context) error {
m.store.Flush()
return nil
}
type memoryStore struct {
store *cache.Cache
opts Options
}
func (m *memoryStore) key(prefix, key string) string {
return prefix + m.opts.Separator + key
}
func (m *memoryStore) exists(prefix, key string) error {
key = m.key(prefix, key)
_, found := m.store.Get(key)
if !found {
return ErrNotFound
}
return nil
}
func (m *memoryStore) get(prefix, key string, val interface{}) error {
key = m.key(prefix, key)
r, found := m.store.Get(key)
if !found {
return ErrNotFound
}
buf, ok := r.([]byte)
if !ok {
return ErrNotFound
}
return m.opts.Codec.Unmarshal(buf, val)
}
func (m *memoryStore) delete(prefix, key string) {
key = m.key(prefix, key)
m.store.Delete(key)
}
func (m *memoryStore) list(prefix string, limit, offset uint) []string {
allItems := m.store.Items()
allKeys := make([]string, 0, len(allItems))
for k := range allItems {
if !strings.HasPrefix(k, prefix) {
continue
}
k = strings.TrimPrefix(k, prefix)
if k[0] == '/' {
k = k[1:]
}
allKeys = append(allKeys, k)
}
if limit != 0 || offset != 0 {
sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })
sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })
end := len(allKeys)
if limit > 0 {
calcLimit := int(offset + limit)
if calcLimit < end {
end = calcLimit
}
}
if int(offset) >= end {
return nil
}
return allKeys[offset:end]
}
return allKeys
}
func (m *memoryStore) Init(opts ...Option) error {
for _, o := range opts {
o(&m.opts)
}
return nil
}
func (m *memoryStore) String() string {
return "memory"
}
func (m *memoryStore) Name() string {
return m.opts.Name
}
func (m *memoryStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
options := NewExistsOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.exists(options.Namespace, key)
}
func (m *memoryStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
options := NewReadOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.get(options.Namespace, key, val)
}
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
options := NewWriteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
if options.TTL == 0 {
options.TTL = cache.NoExpiration
}
key = m.key(options.Namespace, key)
buf, err := m.opts.Codec.Marshal(val)
if err != nil {
return err
}
m.store.Set(key, buf, options.TTL)
return nil
}
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
options := NewDeleteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
m.delete(options.Namespace, key)
return nil
}
func (m *memoryStore) Options() Options {
return m.opts
}
func (m *memoryStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
options := NewListOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
keys := m.list(options.Namespace, options.Limit, options.Offset)
if len(options.Prefix) > 0 {
var prefixKeys []string
for _, k := range keys {
if strings.HasPrefix(k, options.Prefix) {
prefixKeys = append(prefixKeys, k)
}
}
keys = prefixKeys
}
if len(options.Suffix) > 0 {
var suffixKeys []string
for _, k := range keys {
if strings.HasSuffix(k, options.Suffix) {
suffixKeys = append(suffixKeys, k)
}
}
keys = suffixKeys
}
return keys, nil
}

306
store/memory/memory.go Normal file
View File

@ -0,0 +1,306 @@
package memory
import (
"context"
"sort"
"strings"
"sync/atomic"
"time"
cache "github.com/patrickmn/go-cache"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/store"
)
// NewStore returns a memory store
func NewStore(opts ...store.Option) store.Store {
return &memoryStore{
opts: store.NewOptions(opts...),
store: cache.New(cache.NoExpiration, 5*time.Minute),
}
}
func (m *memoryStore) Connect(ctx context.Context) error {
if m.opts.LazyConnect {
return nil
}
return m.connect(ctx)
}
func (m *memoryStore) Disconnect(ctx context.Context) error {
m.store.Flush()
return nil
}
type memoryStore struct {
funcRead store.FuncRead
funcWrite store.FuncWrite
funcExists store.FuncExists
funcList store.FuncList
funcDelete store.FuncDelete
store *cache.Cache
opts store.Options
isConnected atomic.Int32
}
func (m *memoryStore) key(prefix, key string) string {
return prefix + m.opts.Separator + key
}
func (m *memoryStore) exists(prefix, key string) error {
key = m.key(prefix, key)
_, found := m.store.Get(key)
if !found {
return store.ErrNotFound
}
return nil
}
func (m *memoryStore) get(prefix, key string, val interface{}) error {
key = m.key(prefix, key)
r, found := m.store.Get(key)
if !found {
return store.ErrNotFound
}
buf, ok := r.([]byte)
if !ok {
return store.ErrNotFound
}
return m.opts.Codec.Unmarshal(buf, val)
}
func (m *memoryStore) delete(prefix, key string) {
key = m.key(prefix, key)
m.store.Delete(key)
}
func (m *memoryStore) list(prefix string, limit, offset uint) []string {
allItems := m.store.Items()
allKeys := make([]string, 0, len(allItems))
for k := range allItems {
if !strings.HasPrefix(k, prefix) {
continue
}
k = strings.TrimPrefix(k, prefix)
if k[0] == '/' {
k = k[1:]
}
allKeys = append(allKeys, k)
}
if limit != 0 || offset != 0 {
sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })
sort.Slice(allKeys, func(i, j int) bool { return allKeys[i] < allKeys[j] })
end := len(allKeys)
if limit > 0 {
calcLimit := int(offset + limit)
if calcLimit < end {
end = calcLimit
}
}
if int(offset) >= end {
return nil
}
return allKeys[offset:end]
}
return allKeys
}
func (m *memoryStore) Init(opts ...store.Option) error {
for _, o := range opts {
o(&m.opts)
}
m.funcRead = m.fnRead
m.funcWrite = m.fnWrite
m.funcExists = m.fnExists
m.funcList = m.fnList
m.funcDelete = m.fnDelete
m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case store.HookRead:
m.funcRead = h(m.funcRead)
case store.HookWrite:
m.funcWrite = h(m.funcWrite)
case store.HookExists:
m.funcExists = h(m.funcExists)
case store.HookList:
m.funcList = h(m.funcList)
case store.HookDelete:
m.funcDelete = h(m.funcDelete)
}
})
return nil
}
func (m *memoryStore) String() string {
return "memory"
}
func (m *memoryStore) Name() string {
return m.opts.Name
}
func (m *memoryStore) Live() bool {
return true
}
func (m *memoryStore) Ready() bool {
return true
}
func (m *memoryStore) Health() bool {
return true
}
func (m *memoryStore) Exists(ctx context.Context, key string, opts ...store.ExistsOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcExists(ctx, key, opts...)
}
func (m *memoryStore) fnExists(ctx context.Context, key string, opts ...store.ExistsOption) error {
options := store.NewExistsOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.exists(options.Namespace, key)
}
func (m *memoryStore) Read(ctx context.Context, key string, val interface{}, opts ...store.ReadOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcRead(ctx, key, val, opts...)
}
func (m *memoryStore) fnRead(ctx context.Context, key string, val interface{}, opts ...store.ReadOption) error {
options := store.NewReadOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.get(options.Namespace, key, val)
}
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...store.WriteOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcWrite(ctx, key, val, opts...)
}
func (m *memoryStore) fnWrite(ctx context.Context, key string, val interface{}, opts ...store.WriteOption) error {
options := store.NewWriteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
if options.TTL == 0 {
options.TTL = cache.NoExpiration
}
key = m.key(options.Namespace, key)
buf, err := m.opts.Codec.Marshal(val)
if err != nil {
return err
}
m.store.Set(key, buf, options.TTL)
return nil
}
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...store.DeleteOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcDelete(ctx, key, opts...)
}
func (m *memoryStore) fnDelete(ctx context.Context, key string, opts ...store.DeleteOption) error {
options := store.NewDeleteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
m.delete(options.Namespace, key)
return nil
}
func (m *memoryStore) Options() store.Options {
return m.opts
}
func (m *memoryStore) List(ctx context.Context, opts ...store.ListOption) ([]string, error) {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return nil, err
}
}
return m.funcList(ctx, opts...)
}
func (m *memoryStore) fnList(ctx context.Context, opts ...store.ListOption) ([]string, error) {
options := store.NewListOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
keys := m.list(options.Namespace, options.Limit, options.Offset)
if len(options.Prefix) > 0 {
var prefixKeys []string
for _, k := range keys {
if strings.HasPrefix(k, options.Prefix) {
prefixKeys = append(prefixKeys, k)
}
}
keys = prefixKeys
}
if len(options.Suffix) > 0 {
var suffixKeys []string
for _, k := range keys {
if strings.HasSuffix(k, options.Suffix) {
suffixKeys = append(suffixKeys, k)
}
}
keys = suffixKeys
}
return keys, nil
}
func (m *memoryStore) connect(ctx context.Context) error {
m.isConnected.CompareAndSwap(0, 1)
return nil
}
func (m *memoryStore) Watch(ctx context.Context, opts ...store.WatchOption) (store.Watcher, error) {
return &watcher{}, nil
}
type watcher struct{}
func (w *watcher) Next() (store.Event, error) {
return nil, nil
}
func (w *watcher) Stop() {
}

View File

@ -1,4 +1,4 @@
package store_test
package memory
import (
"context"
@ -8,8 +8,41 @@ import (
"go.unistack.org/micro/v3/store"
)
type testHook struct {
f bool
}
func (t *testHook) Exists(fn store.FuncExists) store.FuncExists {
return func(ctx context.Context, key string, opts ...store.ExistsOption) error {
t.f = true
return fn(ctx, key, opts...)
}
}
func TestHook(t *testing.T) {
h := &testHook{}
s := NewStore(store.Hooks(store.HookExists(h.Exists)))
if err := s.Init(); err != nil {
t.Fatal(err)
}
if err := s.Write(context.TODO(), "test", nil); err != nil {
t.Error(err)
}
if err := s.Exists(context.TODO(), "test"); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}
func TestMemoryReInit(t *testing.T) {
s := store.NewStore(store.Namespace("aaa"))
s := NewStore(store.Namespace("aaa"))
if err := s.Init(store.Namespace("")); err != nil {
t.Fatal(err)
}
@ -19,7 +52,7 @@ func TestMemoryReInit(t *testing.T) {
}
func TestMemoryBasic(t *testing.T) {
s := store.NewStore()
s := NewStore()
if err := s.Init(); err != nil {
t.Fatal(err)
}
@ -27,7 +60,7 @@ func TestMemoryBasic(t *testing.T) {
}
func TestMemoryPrefix(t *testing.T) {
s := store.NewStore()
s := NewStore()
if err := s.Init(store.Namespace("some-prefix")); err != nil {
t.Fatal(err)
}
@ -35,7 +68,7 @@ func TestMemoryPrefix(t *testing.T) {
}
func TestMemoryNamespace(t *testing.T) {
s := store.NewStore()
s := NewStore()
if err := s.Init(store.Namespace("some-namespace")); err != nil {
t.Fatal(err)
}
@ -43,7 +76,7 @@ func TestMemoryNamespace(t *testing.T) {
}
func TestMemoryNamespacePrefix(t *testing.T) {
s := store.NewStore()
s := NewStore()
if err := s.Init(store.Namespace("some-namespace")); err != nil {
t.Fatal(err)
}

238
store/noop.go Normal file
View File

@ -0,0 +1,238 @@
package store
import (
"context"
"sync"
"sync/atomic"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/util/id"
)
var _ Store = (*noopStore)(nil)
type noopStore struct {
mu sync.Mutex
watchers map[string]Watcher
funcRead FuncRead
funcWrite FuncWrite
funcExists FuncExists
funcList FuncList
funcDelete FuncDelete
opts Options
isConnected atomic.Int32
}
func (n *noopStore) Live() bool {
return true
}
func (n *noopStore) Ready() bool {
return true
}
func (n *noopStore) Health() bool {
return true
}
func NewStore(opts ...Option) *noopStore {
options := NewOptions(opts...)
return &noopStore{opts: options}
}
func (n *noopStore) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
n.funcRead = n.fnRead
n.funcWrite = n.fnWrite
n.funcExists = n.fnExists
n.funcList = n.fnList
n.funcDelete = n.fnDelete
n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) {
case HookRead:
n.funcRead = h(n.funcRead)
case HookWrite:
n.funcWrite = h(n.funcWrite)
case HookExists:
n.funcExists = h(n.funcExists)
case HookList:
n.funcList = h(n.funcList)
case HookDelete:
n.funcDelete = h(n.funcDelete)
}
})
return nil
}
func (n *noopStore) Connect(ctx context.Context) error {
if n.opts.LazyConnect {
return nil
}
return n.connect(ctx)
}
func (n *noopStore) Disconnect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
func (n *noopStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcRead(ctx, key, val, opts...)
}
func (n *noopStore) fnRead(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
func (n *noopStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcDelete(ctx, key, opts...)
}
func (n *noopStore) fnDelete(ctx context.Context, key string, opts ...DeleteOption) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
func (n *noopStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcExists(ctx, key, opts...)
}
func (n *noopStore) fnExists(ctx context.Context, key string, opts ...ExistsOption) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
func (n *noopStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcWrite(ctx, key, val, opts...)
}
func (n *noopStore) fnWrite(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return nil
}
func (n *noopStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return nil, err
}
}
return n.funcList(ctx, opts...)
}
func (n *noopStore) fnList(ctx context.Context, opts ...ListOption) ([]string, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return nil, nil
}
func (n *noopStore) Name() string {
return n.opts.Name
}
func (n *noopStore) String() string {
return "noop"
}
func (n *noopStore) Options() Options {
return n.opts
}
func (n *noopStore) connect(ctx context.Context) error {
if n.isConnected.CompareAndSwap(0, 1) {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
}
return nil
}
type watcher struct {
exit chan bool
id string
ch chan Event
opts WatchOptions
}
func (m *noopStore) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
id, err := id.New()
if err != nil {
return nil, err
}
wo, err := NewWatchOptions(opts...)
if err != nil {
return nil, err
}
// construct the watcher
w := &watcher{
exit: make(chan bool),
ch: make(chan Event),
id: id,
opts: wo,
}
m.mu.Lock()
m.watchers[w.id] = w
m.mu.Unlock()
return w, nil
}
func (w *watcher) Next() (Event, error) {
return nil, nil
}
func (w *watcher) Stop() {
}

35
store/noop_test.go Normal file
View File

@ -0,0 +1,35 @@
package store
import (
"context"
"testing"
)
type testHook struct {
f bool
}
func (t *testHook) Exists(fn FuncExists) FuncExists {
return func(ctx context.Context, key string, opts ...ExistsOption) error {
t.f = true
return fn(ctx, key, opts...)
}
}
func TestHook(t *testing.T) {
h := &testHook{}
s := NewStore(Hooks(HookExists(h.Exists)))
if err := s.Init(); err != nil {
t.Fatal(err)
}
if err := s.Exists(context.TODO(), "test"); err != nil {
t.Fatal(err)
}
if !h.f {
t.Fatal("hook not works")
}
}

View File

@ -9,6 +9,7 @@ import (
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/tracer"
)
@ -38,6 +39,10 @@ type Options struct {
// Wrappers []Wrapper
// Timeout specifies timeout duration for all operations
Timeout time.Duration
// Hooks can be run before/after store Read/List/Write/Exists/Delete
Hooks options.Hooks
// LazyConnect creates a connection when using store
LazyConnect bool
}
// NewOptions creates options struct
@ -129,6 +134,13 @@ func Timeout(td time.Duration) Option {
}
}
// LazyConnect initialize connection only when needed
func LazyConnect(b bool) Option {
return func(o *Options) {
o.LazyConnect = b
}
}
// Addrs contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings.
@ -144,6 +156,10 @@ type ReadOptions struct {
Context context.Context
// Namespace holds namespace
Namespace string
// Name holds mnemonic name
Name string
// Timeout specifies max timeout for operation
Timeout time.Duration
}
// NewReadOptions fills ReadOptions struct with opts slice
@ -158,6 +174,20 @@ func NewReadOptions(opts ...ReadOption) ReadOptions {
// ReadOption sets values in ReadOptions
type ReadOption func(r *ReadOptions)
// ReadTimeout pass timeout to ReadOptions
func ReadTimeout(td time.Duration) ReadOption {
return func(o *ReadOptions) {
o.Timeout = td
}
}
// ReadName pass name to ReadOptions
func ReadName(name string) ReadOption {
return func(o *ReadOptions) {
o.Name = name
}
}
// ReadContext pass context.Context to ReadOptions
func ReadContext(ctx context.Context) ReadOption {
return func(o *ReadOptions) {
@ -180,6 +210,10 @@ type WriteOptions struct {
Metadata metadata.Metadata
// Namespace holds namespace
Namespace string
// Name holds mnemonic name
Name string
// Timeout specifies max timeout for operation
Timeout time.Duration
// TTL specifies key TTL
TTL time.Duration
}
@ -224,12 +258,30 @@ func WriteNamespace(ns string) WriteOption {
}
}
// WriteName pass name to WriteOptions
func WriteName(name string) WriteOption {
return func(o *WriteOptions) {
o.Name = name
}
}
// WriteTimeout pass timeout to WriteOptions
func WriteTimeout(td time.Duration) WriteOption {
return func(o *WriteOptions) {
o.Timeout = td
}
}
// DeleteOptions configures an individual Delete operation
type DeleteOptions struct {
// Context holds external options
Context context.Context
// Namespace holds namespace
Namespace string
// Name holds mnemonic name
Name string
// Timeout specifies max timeout for operation
Timeout time.Duration
}
// NewDeleteOptions fills DeleteOptions struct with opts slice
@ -258,14 +310,32 @@ func DeleteNamespace(ns string) DeleteOption {
}
}
// DeleteName pass name to DeleteOptions
func DeleteName(name string) DeleteOption {
return func(o *DeleteOptions) {
o.Name = name
}
}
// DeleteTimeout pass timeout to DeleteOptions
func DeleteTimeout(td time.Duration) DeleteOption {
return func(o *DeleteOptions) {
o.Timeout = td
}
}
// ListOptions configures an individual List operation
type ListOptions struct {
Context context.Context
Prefix string
Suffix string
Namespace string
Limit uint
Offset uint
// Name holds mnemonic name
Name string
Limit uint
Offset uint
// Timeout specifies max timeout for operation
Timeout time.Duration
}
// NewListOptions fills ListOptions struct with opts slice
@ -322,12 +392,23 @@ func ListNamespace(ns string) ListOption {
}
}
// ListTimeout pass timeout to ListOptions
func ListTimeout(td time.Duration) ListOption {
return func(o *ListOptions) {
o.Timeout = td
}
}
// ExistsOptions holds options for Exists method
type ExistsOptions struct {
// Context holds external options
Context context.Context
// Namespace contains namespace
Namespace string
// Name holds mnemonic name
Name string
// Timeout specifies max timeout for operation
Timeout time.Duration
}
// ExistsOption specifies Exists call options
@ -358,11 +439,23 @@ func ExistsNamespace(ns string) ExistsOption {
}
}
/*
// WrapStore adds a store Wrapper to a list of options passed into the store
func WrapStore(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
// ExistsName pass name to exist options
func ExistsName(name string) ExistsOption {
return func(o *ExistsOptions) {
o.Name = name
}
}
// ExistsTimeout timeout to ListOptions
func ExistsTimeout(td time.Duration) ExistsOption {
return func(o *ExistsOptions) {
o.Timeout = td
}
}
// Hooks sets hook runs before action
func Hooks(h ...options.Hook) Option {
return func(o *Options) {
o.Hooks = append(o.Hooks, h...)
}
}
*/

View File

@ -1,9 +1,10 @@
// Package store is an interface for distributed data storage.
package store // import "go.unistack.org/micro/v3/store"
package store
import (
"context"
"errors"
"time"
)
type EventType int
@ -15,6 +16,9 @@ const (
)
var (
ErrWatcherStopped = errors.New("watcher stopped")
// ErrNotConnected is returned when a store is not connected
ErrNotConnected = errors.New("not conected")
// ErrNotFound is returned when a key doesn't exist
ErrNotFound = errors.New("not found")
// ErrInvalidKey is returned when a key has empty or have invalid format
@ -32,6 +36,7 @@ type Event interface {
// Store is a data storage interface
type Store interface {
// Name returns store name
Name() string
// Init initialises the store
Init(opts ...Option) error
@ -53,6 +58,65 @@ type Store interface {
Disconnect(ctx context.Context) error
// String returns the name of the implementation.
String() string
// Watch returns events watcher
Watch(ctx context.Context, opts ...WatchOption) (Watcher, error)
// Live returns store liveness
Live() bool
// Ready returns store readiness
Ready() bool
// Health returns store health
Health() bool
}
type (
FuncExists func(ctx context.Context, key string, opts ...ExistsOption) error
HookExists func(next FuncExists) FuncExists
FuncRead func(ctx context.Context, key string, val interface{}, opts ...ReadOption) error
HookRead func(next FuncRead) FuncRead
FuncWrite func(ctx context.Context, key string, val interface{}, opts ...WriteOption) error
HookWrite func(next FuncWrite) FuncWrite
FuncDelete func(ctx context.Context, key string, opts ...DeleteOption) error
HookDelete func(next FuncDelete) FuncDelete
FuncList func(ctx context.Context, opts ...ListOption) ([]string, error)
HookList func(next FuncList) FuncList
)
type EventType int
const (
EventTypeUnknown = iota
EventTypeConnect
EventTypeDisconnect
EventTypeOpError
)
type Event interface {
Timestamp() time.Time
Error() error
Type() EventType
}
type Watcher interface {
// Next is a blocking call
Next() (Event, error)
// Stop stops the watcher
Stop()
}
type WatchOption func(*WatchOptions) error
type WatchOptions struct{}
func NewWatchOptions(opts ...WatchOption) (WatchOptions, error) {
options := WatchOptions{}
var err error
for _, o := range opts {
if err = o(&options); err != nil {
break
}
}
return options, err
}
type Watcher interface {

View File

@ -67,16 +67,18 @@ func (w *NamespaceStore) String() string {
return w.s.String()
}
// type NamespaceWrapper struct{}
// func NewNamespaceWrapper() Wrapper {
// return &NamespaceWrapper{}
// }
/*
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
func (w *NamespaceStore) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
return w.s.Watch(ctx, opts...)
}
func (w *NamespaceStore) Live() bool {
return w.s.Live()
}
func (w *NamespaceStore) Ready() bool {
return w.s.Ready()
}
func (w *NamespaceStore) Health() bool {
return w.s.Health()
}
*/

View File

@ -1,5 +1,5 @@
// Package sync is an interface for distributed synchronization
package sync // import "go.unistack.org/micro/v3/sync"
package sync
import (
"errors"

View File

@ -17,14 +17,14 @@ func TestLoggerWithTracer(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger.DefaultLogger = slog.NewLogger(logger.WithOutput(buf))
if err := logger.Init(); err != nil {
if err := logger.DefaultLogger.Init(); err != nil {
t.Fatal(err)
}
var span tracer.Span
tr := NewTracer()
ctx, span = tr.Start(ctx, "test1")
logger.Error(ctx, "my test error", fmt.Errorf("error"))
logger.DefaultLogger.Error(ctx, "my test error", fmt.Errorf("error"))
if !strings.Contains(buf.String(), span.TraceID()) {
t.Fatalf("log does not contains trace id: %s", buf.Bytes())

View File

@ -83,8 +83,11 @@ func (sk SpanKind) String() string {
// SpanOptions contains span option
type SpanOptions struct {
Labels []interface{}
Kind SpanKind
StatusMsg string
Labels []interface{}
Status SpanStatus
Kind SpanKind
Record bool
}
// SpanOption func signature
@ -110,12 +113,25 @@ func WithSpanLabels(kv ...interface{}) SpanOption {
}
}
func WithSpanStatus(st SpanStatus, msg string) SpanOption {
return func(o *SpanOptions) {
o.Status = st
o.StatusMsg = msg
}
}
func WithSpanKind(k SpanKind) SpanOption {
return func(o *SpanOptions) {
o.Kind = k
}
}
func WithSpanRecord(b bool) SpanOption {
return func(o *SpanOptions) {
o.Record = b
}
}
// Options struct
type Options struct {
// Context used to store custome tracer options
@ -124,6 +140,8 @@ type Options struct {
Logger logger.Logger
// Name of the tracer
Name string
// ContextAttrFuncs contains funcs that provides tracing
ContextAttrFuncs []ContextAttrFunc
}
// Option func signature
@ -148,7 +166,8 @@ func NewEventOptions(opts ...EventOption) EventOptions {
// NewSpanOptions returns default SpanOptions
func NewSpanOptions(opts ...SpanOption) SpanOptions {
options := SpanOptions{
Kind: SpanKindInternal,
Kind: SpanKindInternal,
Record: true,
}
for _, o := range opts {
o(&options)
@ -159,8 +178,9 @@ func NewSpanOptions(opts ...SpanOption) SpanOptions {
// NewOptions returns default options
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Context: context.Background(),
Logger: logger.DefaultLogger,
Context: context.Background(),
ContextAttrFuncs: DefaultContextAttrFuncs,
}
for _, o := range opts {
o(&options)

View File

@ -1,5 +1,5 @@
// Package tracer provides an interface for distributed tracing
package tracer // import "go.unistack.org/micro/v3/tracer"
package tracer
import (
"context"
@ -7,16 +7,25 @@ import (
"go.unistack.org/micro/v3/logger"
)
// DefaultTracer is the global default tracer
var DefaultTracer Tracer = NewTracer()
var (
// DefaultTracer is the global default tracer
DefaultTracer Tracer = NewTracer() //nolint:revive
// TraceIDKey is the key used for the trace id in the log call
TraceIDKey = "trace-id"
// SpanIDKey is the key used for the span id in the log call
SpanIDKey = "span-id"
// DefaultSkipEndpoints is the slice of endpoint that must not be traced
DefaultSkipEndpoints = []string{
"MeterService.Metrics",
"HealthService.Live",
"HealthService.Ready",
"HealthService.Version",
}
DefaultContextAttrFuncs []ContextAttrFunc
)
type ContextAttrFunc func(ctx context.Context) []interface{}
func init() {
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs,
func(ctx context.Context) []interface{} {
@ -38,6 +47,8 @@ type Tracer interface {
Init(...Option) error
// Start a trace
Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span)
// Extract get span metadata from context
// Extract(ctx context.Context)
// Flush flushes spans
Flush(ctx context.Context) error
}

View File

@ -1,13 +0,0 @@
package tracer
import (
"testing"
)
func TestUniqLabels(t *testing.T) {
labels := []interface{}{"key1", "val1", "key1", "val2"}
labels = UniqLabels(labels)
if labels[1] != "val2" {
t.Fatalf("UniqLabels not works")
}
}

View File

@ -1,415 +0,0 @@
// Package wrapper provides wrapper for Tracer
package wrapper // import "go.unistack.org/micro/v3/tracer/wrapper"
import (
"context"
"fmt"
"strings"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/tracer"
)
var DefaultHeadersExctract = []string{metadata.HeaderXRequestID}
func ExtractDefaultLabels(md metadata.Metadata) []interface{} {
labels := make([]interface{}, 0, len(DefaultHeadersExctract))
for _, k := range DefaultHeadersExctract {
if v, ok := md.Get(k); ok {
labels = append(labels, strings.ToLower(k), v)
}
}
return labels
}
var (
DefaultClientCallObserver = func(ctx context.Context, req client.Request, rsp interface{}, opts []client.CallOption, sp tracer.Span, err error) {
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = append(labels, ExtractDefaultLabels(md)...)
}
if err != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
sp.AddLabels(labels...)
}
DefaultClientStreamObserver = func(ctx context.Context, req client.Request, opts []client.CallOption, stream client.Stream, sp tracer.Span, err error) {
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = append(labels, ExtractDefaultLabels(md)...)
}
if err != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
sp.AddLabels(labels...)
}
DefaultClientPublishObserver = func(ctx context.Context, msg client.Message, opts []client.PublishOption, sp tracer.Span, err error) {
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = append(labels, ExtractDefaultLabels(md)...)
}
labels = append(labels, ExtractDefaultLabels(msg.Metadata())...)
if err != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
sp.AddLabels(labels...)
}
DefaultServerHandlerObserver = func(ctx context.Context, req server.Request, rsp interface{}, sp tracer.Span, err error) {
var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = append(labels, ExtractDefaultLabels(md)...)
}
if err != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
sp.AddLabels(labels...)
}
DefaultServerSubscriberObserver = func(ctx context.Context, msg server.Message, sp tracer.Span, err error) {
var labels []interface{}
if md, ok := metadata.FromIncomingContext(ctx); ok {
labels = append(labels, ExtractDefaultLabels(md)...)
}
labels = append(labels, ExtractDefaultLabels(msg.Header())...)
if err != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
sp.AddLabels(labels...)
}
DefaultClientCallFuncObserver = func(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions, sp tracer.Span, err error) {
sp.SetName(fmt.Sprintf("%s.%s call", req.Service(), req.Method()))
var labels []interface{}
if md, ok := metadata.FromOutgoingContext(ctx); ok {
labels = append(labels, ExtractDefaultLabels(md)...)
}
if err != nil {
sp.SetStatus(tracer.SpanStatusError, err.Error())
}
sp.AddLabels(labels...)
}
DefaultSkipEndpoints = []string{"Meter.Metrics", "Health.Live", "Health.Ready", "Health.Version"}
)
type tWrapper struct {
client.Client
serverHandler server.HandlerFunc
serverSubscriber server.SubscriberFunc
clientCallFunc client.CallFunc
opts Options
}
type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, tracer.Span, error)
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, tracer.Span, error)
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, tracer.Span, error)
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, tracer.Span, error)
ServerHandlerObserver func(context.Context, server.Request, interface{}, tracer.Span, error)
ServerSubscriberObserver func(context.Context, server.Message, tracer.Span, error)
)
// Options struct
type Options struct {
// Tracer that used for tracing
Tracer tracer.Tracer
// ClientCallObservers funcs
ClientCallObservers []ClientCallObserver
// ClientStreamObservers funcs
ClientStreamObservers []ClientStreamObserver
// ClientPublishObservers funcs
ClientPublishObservers []ClientPublishObserver
// ClientCallFuncObservers funcs
ClientCallFuncObservers []ClientCallFuncObserver
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// SkipEndpoints
SkipEndpoints []string
}
// Option func signature
type Option func(*Options)
// NewOptions create Options from Option slice
func NewOptions(opts ...Option) Options {
options := Options{
Tracer: tracer.DefaultTracer,
ClientCallObservers: []ClientCallObserver{DefaultClientCallObserver},
ClientStreamObservers: []ClientStreamObserver{DefaultClientStreamObserver},
ClientPublishObservers: []ClientPublishObserver{DefaultClientPublishObserver},
ClientCallFuncObservers: []ClientCallFuncObserver{DefaultClientCallFuncObserver},
ServerHandlerObservers: []ServerHandlerObserver{DefaultServerHandlerObserver},
ServerSubscriberObservers: []ServerSubscriberObserver{DefaultServerSubscriberObserver},
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
o(&options)
}
return options
}
// WithTracer pass tracer
func WithTracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// SkipEndponts
func SkipEndpoins(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
// WithClientCallObservers funcs
func WithClientCallObservers(ob ...ClientCallObserver) Option {
return func(o *Options) {
o.ClientCallObservers = ob
}
}
// WithClientStreamObservers funcs
func WithClientStreamObservers(ob ...ClientStreamObserver) Option {
return func(o *Options) {
o.ClientStreamObservers = ob
}
}
// WithClientPublishObservers funcs
func WithClientPublishObservers(ob ...ClientPublishObserver) Option {
return func(o *Options) {
o.ClientPublishObservers = ob
}
}
// WithClientCallFuncObservers funcs
func WithClientCallFuncObservers(ob ...ClientCallFuncObserver) Option {
return func(o *Options) {
o.ClientCallFuncObservers = ob
}
}
// WithServerHandlerObservers funcs
func WithServerHandlerObservers(ob ...ServerHandlerObserver) Option {
return func(o *Options) {
o.ServerHandlerObservers = ob
}
}
// WithServerSubscriberObservers funcs
func WithServerSubscriberObservers(ob ...ServerSubscriberObserver) Option {
return func(o *Options) {
o.ServerSubscriberObservers = ob
}
}
func (ot *tWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint {
return ot.Client.Call(ctx, req, rsp, opts...)
}
}
nctx, sp := ot.opts.Tracer.Start(ctx, fmt.Sprintf("%s.%s rpc-client", req.Service(), req.Method()),
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels(
"rpc.service", req.Service(),
"rpc.method", req.Method(),
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", "unary",
),
)
defer sp.Finish()
err := ot.Client.Call(nctx, req, rsp, opts...)
for _, o := range ot.opts.ClientCallObservers {
o(nctx, req, rsp, opts, sp, err)
}
return err
}
func (ot *tWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint {
return ot.Client.Stream(ctx, req, opts...)
}
}
nctx, sp := ot.opts.Tracer.Start(ctx, fmt.Sprintf("%s.%s rpc-client", req.Service(), req.Method()),
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels(
"rpc.service", req.Service(),
"rpc.method", req.Method(),
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", "stream",
),
)
defer sp.Finish()
stream, err := ot.Client.Stream(nctx, req, opts...)
for _, o := range ot.opts.ClientStreamObservers {
o(nctx, req, opts, stream, sp, err)
}
return stream, err
}
func (ot *tWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
nctx, sp := ot.opts.Tracer.Start(ctx, msg.Topic()+" publish", tracer.WithSpanKind(tracer.SpanKindProducer))
defer sp.Finish()
sp.AddLabels("messaging.destination.name", msg.Topic())
sp.AddLabels("messaging.operation", "publish")
err := ot.Client.Publish(nctx, msg, opts...)
for _, o := range ot.opts.ClientPublishObservers {
o(nctx, msg, opts, sp, err)
}
return err
}
func (ot *tWrapper) ServerHandler(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Method())
for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint {
return ot.serverHandler(ctx, req, rsp)
}
}
callType := "unary"
if req.Stream() {
callType = "stream"
}
nctx, sp := ot.opts.Tracer.Start(ctx, fmt.Sprintf("%s.%s rpc-server", req.Service(), req.Method()),
tracer.WithSpanKind(tracer.SpanKindServer),
tracer.WithSpanLabels(
"rpc.service", req.Service(),
"rpc.method", req.Method(),
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", callType,
),
)
defer sp.Finish()
err := ot.serverHandler(nctx, req, rsp)
for _, o := range ot.opts.ServerHandlerObservers {
o(nctx, req, rsp, sp, err)
}
return err
}
func (ot *tWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
nctx, sp := ot.opts.Tracer.Start(ctx, msg.Topic()+" process", tracer.WithSpanKind(tracer.SpanKindConsumer))
defer sp.Finish()
sp.AddLabels("messaging.operation", "process")
sp.AddLabels("messaging.source.name", msg.Topic())
err := ot.serverSubscriber(nctx, msg)
for _, o := range ot.opts.ServerSubscriberObservers {
o(nctx, msg, sp, err)
}
return err
}
// NewClientWrapper accepts an open tracing Trace and returns a Client Wrapper
func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client {
options := NewOptions()
for _, o := range opts {
o(&options)
}
return &tWrapper{opts: options, Client: c}
}
}
// NewClientCallWrapper accepts an opentracing Tracer and returns a Call Wrapper
func NewClientCallWrapper(opts ...Option) client.CallWrapper {
return func(h client.CallFunc) client.CallFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
ot := &tWrapper{opts: options, clientCallFunc: h}
return ot.ClientCallFunc
}
}
func (ot *tWrapper) ClientCallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Method())
for _, ep := range ot.opts.SkipEndpoints {
if ep == endpoint {
return ot.ClientCallFunc(ctx, addr, req, rsp, opts)
}
}
nctx, sp := ot.opts.Tracer.Start(ctx, fmt.Sprintf("%s.%s rpc-client", req.Service(), req.Method()),
tracer.WithSpanKind(tracer.SpanKindClient),
tracer.WithSpanLabels(
"rpc.service", req.Service(),
"rpc.method", req.Method(),
"rpc.flavor", "rpc",
"rpc.call", "/"+req.Service()+"/"+req.Endpoint(),
"rpc.call_type", "unary",
),
)
defer sp.Finish()
err := ot.clientCallFunc(nctx, addr, req, rsp, opts)
for _, o := range ot.opts.ClientCallFuncObservers {
o(nctx, addr, req, rsp, opts, sp, err)
}
return err
}
// NewServerHandlerWrapper accepts an options and returns a Handler Wrapper
func NewServerHandlerWrapper(opts ...Option) server.HandlerWrapper {
return func(h server.HandlerFunc) server.HandlerFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
ot := &tWrapper{opts: options, serverHandler: h}
return ot.ServerHandler
}
}
// NewServerSubscriberWrapper accepts an opentracing Tracer and returns a Subscriber Wrapper
func NewServerSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
return func(h server.SubscriberFunc) server.SubscriberFunc {
options := NewOptions()
for _, o := range opts {
o(&options)
}
ot := &tWrapper{opts: options, serverSubscriber: h}
return ot.ServerSubscriber
}
}

View File

@ -1,4 +1,4 @@
package addr // import "go.unistack.org/micro/v3/util/addr"
package addr
import (
"fmt"
@ -58,6 +58,7 @@ func IsLocal(addr string) bool {
}
// Extract returns a real ip
//
//nolint:gocyclo
func Extract(addr string) (string, error) {
// if addr specified then its returned

View File

@ -1,5 +1,5 @@
// Package backoff provides backoff functionality
package backoff // import "go.unistack.org/micro/v3/util/backoff"
package backoff
import (
"math"

View File

@ -1,4 +1,4 @@
package buf // import "go.unistack.org/micro/v3/util/buf"
package buf
import (
"bytes"

377
util/dns/cache.go Normal file
View File

@ -0,0 +1,377 @@
package dns
import (
"context"
"math"
"net"
"sync"
"time"
)
// DialFunc is a [net.Resolver.Dial] function.
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
// NewNetResolver creates a caching [net.Resolver] that uses parent to resolve names.
func NewNetResolver(opts ...Option) *net.Resolver {
options := Options{Resolver: &net.Resolver{}}
for _, o := range opts {
o(&options)
}
return &net.Resolver{
PreferGo: true,
StrictErrors: options.Resolver.StrictErrors,
Dial: NewNetDialer(options.Resolver.Dial, append(opts, Resolver(options.Resolver))...),
}
}
// NewNetDialer adds caching to a [net.Resolver.Dial] function.
func NewNetDialer(parent DialFunc, opts ...Option) DialFunc {
cache := cache{dial: parent, opts: Options{}}
for _, o := range opts {
o(&cache.opts)
}
if cache.opts.MaxCacheEntries == 0 {
cache.opts.MaxCacheEntries = DefaultMaxCacheEntries
}
return func(ctx context.Context, network, address string) (net.Conn, error) {
conn := &dnsConn{}
conn.roundTrip = cachingRoundTrip(&cache, network, address)
return conn, nil
}
}
const DefaultMaxCacheEntries = 300
// A Option customizes the resolver cache.
type Option func(*Options)
type Options struct {
Resolver *net.Resolver
MaxCacheEntries int
MaxCacheTTL time.Duration
MinCacheTTL time.Duration
NegativeCache bool
PreferIPV4 bool
PreferIPV6 bool
Timeout time.Duration
}
// MaxCacheEntries sets the maximum number of entries to cache.
// If zero, [DefaultMaxCacheEntries] is used; negative means no limit.
func MaxCacheEntries(n int) Option {
return func(o *Options) {
o.MaxCacheEntries = n
}
}
// MaxCacheTTL sets the maximum time-to-live for entries in the cache.
func MaxCacheTTL(td time.Duration) Option {
return func(o *Options) {
o.MaxCacheTTL = td
}
}
// MinCacheTTL sets the minimum time-to-live for entries in the cache.
func MinCacheTTL(td time.Duration) Option {
return func(o *Options) {
o.MinCacheTTL = td
}
}
// NegativeCache sets whether to cache negative responses.
func NegativeCache(b bool) Option {
return func(o *Options) {
o.NegativeCache = b
}
}
// Timeout sets upstream *net.Resolver timeout
func Timeout(td time.Duration) Option {
return func(o *Options) {
o.Timeout = td
}
}
// Resolver sets upstream *net.Resolver.
func Resolver(r *net.Resolver) Option {
return func(o *Options) {
o.Resolver = r
}
}
// PreferIPV4 resolve ipv4 records.
func PreferIPV4(b bool) Option {
return func(o *Options) {
o.PreferIPV4 = b
}
}
// PreferIPV6 resolve ipv4 records.
func PreferIPV6(b bool) Option {
return func(o *Options) {
o.PreferIPV6 = b
}
}
type cache struct {
sync.RWMutex
dial DialFunc
entries map[string]cacheEntry
opts Options
}
type cacheEntry struct {
deadline time.Time
value string
}
func (c *cache) put(req string, res string) {
// ignore uncacheable/unparseable answers
if invalid(req, res) {
return
}
// ignore errors (if requested)
if nameError(res) && !c.opts.NegativeCache {
return
}
// ignore uncacheable/unparseable answers
ttl := getTTL(res)
if ttl <= 0 {
return
}
// adjust TTL
if ttl < c.opts.MinCacheTTL {
ttl = c.opts.MinCacheTTL
}
// maxTTL overrides minTTL
if ttl > c.opts.MaxCacheTTL && c.opts.MaxCacheTTL != 0 {
ttl = c.opts.MaxCacheTTL
}
c.Lock()
defer c.Unlock()
if c.entries == nil {
c.entries = make(map[string]cacheEntry)
}
// do some cache evition
var tested, evicted int
for k, e := range c.entries {
if time.Until(e.deadline) <= 0 {
// delete expired entry
delete(c.entries, k)
evicted++
}
tested++
if tested < 8 {
continue
}
if evicted == 0 && c.opts.MaxCacheEntries > 0 && len(c.entries) >= c.opts.MaxCacheEntries {
// delete at least one entry
delete(c.entries, k)
}
break
}
// remove message IDs
c.entries[req[2:]] = cacheEntry{
deadline: time.Now().Add(ttl),
value: res[2:],
}
}
func (c *cache) get(req string) (res string) {
// ignore invalid messages
if len(req) < 12 {
return ""
}
if req[2] >= 0x7f {
return ""
}
c.RLock()
defer c.RUnlock()
if c.entries == nil {
return ""
}
// remove message ID
entry, ok := c.entries[req[2:]]
if ok && time.Until(entry.deadline) > 0 {
// prepend correct ID
return req[:2] + entry.value
}
return ""
}
func invalid(req string, res string) bool {
if len(req) < 12 || len(res) < 12 { // header size
return true
}
if req[0] != res[0] || req[1] != res[1] { // IDs match
return true
}
if req[2] >= 0x7f || res[2] < 0x7f { // query, response
return true
}
if req[2]&0x7a != 0 || res[2]&0x7a != 0 { // standard query, not truncated
return true
}
if res[3]&0xf != 0 && res[3]&0xf != 3 { // no error, or name error
return true
}
return false
}
func nameError(res string) bool {
return res[3]&0xf == 3
}
func getTTL(msg string) time.Duration {
ttl := math.MaxInt32
qdcount := getUint16(msg[4:])
ancount := getUint16(msg[6:])
nscount := getUint16(msg[8:])
arcount := getUint16(msg[10:])
rdcount := ancount + nscount + arcount
msg = msg[12:] // skip header
// skip questions
for i := 0; i < qdcount; i++ {
name := getNameLen(msg)
if name < 0 || name+4 > len(msg) {
return -1
}
msg = msg[name+4:]
}
// parse records
for i := 0; i < rdcount; i++ {
name := getNameLen(msg)
if name < 0 || name+10 > len(msg) {
return -1
}
rtyp := getUint16(msg[name+0:])
rttl := getUint32(msg[name+4:])
rlen := getUint16(msg[name+8:])
if name+10+rlen > len(msg) {
return -1
}
// skip EDNS OPT since it doesn't have a TTL
if rtyp != 41 && rttl < ttl {
ttl = rttl
}
msg = msg[name+10+rlen:]
}
return time.Duration(ttl) * time.Second
}
func getNameLen(msg string) int {
i := 0
for i < len(msg) {
if msg[i] == 0 {
// end of name
i += 1
break
}
if msg[i] >= 0xc0 {
// compressed name
i += 2
break
}
if msg[i] >= 0x40 {
// reserved
return -1
}
i += int(msg[i] + 1)
}
return i
}
func getUint16(s string) int {
return int(s[1]) | int(s[0])<<8
}
func getUint32(s string) int {
return int(s[3]) | int(s[2])<<8 | int(s[1])<<16 | int(s[0])<<24
}
func cachingRoundTrip(cache *cache, network, address string) roundTripper {
return func(ctx context.Context, req string) (res string, err error) {
// check cache
if res := cache.get(req); res != "" {
return res, nil
}
switch {
case cache.opts.PreferIPV4 && cache.opts.PreferIPV6:
network = "udp"
case cache.opts.PreferIPV4:
network = "udp4"
case cache.opts.PreferIPV6:
network = "udp6"
default:
network = "udp"
}
if cache.opts.Timeout > 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, cache.opts.Timeout)
defer cancel()
}
// dial connection
var conn net.Conn
if cache.dial != nil {
conn, err = cache.dial(ctx, network, address)
} else {
var d net.Dialer
conn, err = d.DialContext(ctx, network, address)
}
if err != nil {
return "", err
}
ctx, cancel := context.WithCancel(ctx)
go func() {
<-ctx.Done()
conn.Close()
}()
defer cancel()
if t, ok := ctx.Deadline(); ok {
err = conn.SetDeadline(t)
if err != nil {
return "", err
}
}
// send request
err = writeMessage(conn, req)
if err != nil {
return "", err
}
// read response
res, err = readMessage(conn)
if err != nil {
return "", err
}
// cache response
cache.put(req, res)
return res, nil
}
}

16
util/dns/cache_test.go Normal file
View File

@ -0,0 +1,16 @@
package dns
import (
"net"
"testing"
)
func TestCache(t *testing.T) {
net.DefaultResolver = NewNetResolver(PreferIPV4(true))
addrs, err := net.LookupHost("unistack.org")
if err != nil {
t.Fatal(err)
}
t.Logf("addrs %v", addrs)
}

178
util/dns/conn.go Normal file
View File

@ -0,0 +1,178 @@
package dns
import (
"bytes"
"context"
"io"
"net"
"strings"
"sync"
"time"
)
type dnsConn struct {
sync.Mutex
ibuf bytes.Buffer
obuf bytes.Buffer
ctx context.Context
cancel context.CancelFunc
deadline time.Time
roundTrip roundTripper
}
type roundTripper func(ctx context.Context, req string) (res string, err error)
func (c *dnsConn) Read(b []byte) (n int, err error) {
imsg, n, err := c.drainBuffers(b)
if n != 0 || err != nil {
return n, err
}
ctx, cancel := c.childContext()
omsg, err := c.roundTrip(ctx, imsg)
cancel()
if err != nil {
return 0, err
}
return c.fillBuffer(b, omsg)
}
func (c *dnsConn) Write(b []byte) (n int, err error) {
c.Lock()
defer c.Unlock()
return c.ibuf.Write(b)
}
func (c *dnsConn) Close() error {
c.Lock()
cancel := c.cancel
c.Unlock()
if cancel != nil {
cancel()
}
return nil
}
func (c *dnsConn) LocalAddr() net.Addr {
return nil
}
func (c *dnsConn) RemoteAddr() net.Addr {
return nil
}
func (c *dnsConn) SetDeadline(t time.Time) error {
c.SetReadDeadline(t)
c.SetWriteDeadline(t)
return nil
}
func (c *dnsConn) SetReadDeadline(t time.Time) error {
c.Lock()
defer c.Unlock()
c.deadline = t
return nil
}
func (c *dnsConn) SetWriteDeadline(t time.Time) error {
// writes do not timeout
return nil
}
func (c *dnsConn) drainBuffers(b []byte) (string, int, error) {
c.Lock()
defer c.Unlock()
// drain the output buffer
if c.obuf.Len() > 0 {
n, err := c.obuf.Read(b)
return "", n, err
}
// otherwise, get the next message from the input buffer
sz := c.ibuf.Next(2)
if len(sz) < 2 {
return "", 0, io.ErrUnexpectedEOF
}
size := int64(sz[0])<<8 | int64(sz[1])
var str strings.Builder
_, err := io.CopyN(&str, &c.ibuf, size)
if err == io.EOF {
return "", 0, io.ErrUnexpectedEOF
}
if err != nil {
return "", 0, err
}
return str.String(), 0, nil
}
func (c *dnsConn) fillBuffer(b []byte, str string) (int, error) {
c.Lock()
defer c.Unlock()
c.obuf.WriteByte(byte(len(str) >> 8))
c.obuf.WriteByte(byte(len(str)))
c.obuf.WriteString(str)
return c.obuf.Read(b)
}
func (c *dnsConn) childContext() (context.Context, context.CancelFunc) {
c.Lock()
defer c.Unlock()
if c.ctx == nil {
c.ctx, c.cancel = context.WithCancel(context.Background())
}
return context.WithDeadline(c.ctx, c.deadline)
}
func writeMessage(conn net.Conn, msg string) error {
var buf []byte
if _, ok := conn.(net.PacketConn); ok {
buf = []byte(msg)
} else {
buf = make([]byte, len(msg)+2)
buf[0] = byte(len(msg) >> 8)
buf[1] = byte(len(msg))
copy(buf[2:], msg)
}
// SHOULD do a single write on TCP (RFC 7766, section 8).
// MUST do a single write on UDP.
_, err := conn.Write(buf)
return err
}
func readMessage(c net.Conn) (string, error) {
if _, ok := c.(net.PacketConn); ok {
// RFC 1035 specifies 512 as the maximum message size for DNS over UDP.
// RFC 6891 OTOH suggests 4096 as the maximum payload size for EDNS.
b := make([]byte, 4096)
n, err := c.Read(b)
if err != nil {
return "", err
}
return string(b[:n]), nil
} else {
var sz [2]byte
_, err := io.ReadFull(c, sz[:])
if err != nil {
return "", err
}
size := int64(sz[0])<<8 | int64(sz[1])
var str strings.Builder
_, err = io.CopyN(&str, c, size)
if err == io.EOF {
return "", io.ErrUnexpectedEOF
}
if err != nil {
return "", err
}
return str.String(), nil
}
}

Some files were not shown because too many files have changed in this diff Show More