Compare commits

...

22 Commits

Author SHA1 Message Date
de4418189d Merge pull request 'add missing option' (#309) from logger-stacktrace into v3
Reviewed-on: #309
2024-03-04 23:04:50 +03:00
2c44550897 add missing option
All checks were successful
pr / test (pull_request) Successful in 1m46s
lint / lint (pull_request) Successful in 10m49s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 23:03:55 +03:00
99b8a3c950 Merge pull request 'logger/slog: add stacktrace support' (#308) from logger-stacktrace into v3
Reviewed-on: #308
2024-03-04 23:00:35 +03:00
4c7e1607d4 logger/slog: add stacktrace support
Some checks failed
pr / test (pull_request) Failing after 1m28s
lint / lint (pull_request) Successful in 10m40s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 22:54:11 +03:00
897be419b4 Merge pull request 'broker noop implementation' (#307) from noops into v3
Reviewed-on: #307
2024-03-04 01:15:16 +03:00
81b9a4341f logger: extend interface, fix tests
All checks were successful
pr / test (pull_request) Successful in 1m35s
lint / lint (pull_request) Successful in 10m40s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 01:09:08 +03:00
d3bb2f7236 broker/noop: add initial implementation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-03-04 01:05:40 +03:00
97fd62cb21 Merge pull request 'register/noop: add noop register' (#306) from register-noop into v3
Reviewed-on: #306
2024-03-01 21:40:01 +03:00
3cd8bc33d6 fixup test
Some checks failed
pr / test (pull_request) Failing after 1m31s
lint / lint (pull_request) Successful in 10m44s
2024-03-01 21:39:31 +03:00
f6f67af8d0 register/noop: add noop register
Some checks failed
pr / test (pull_request) Failing after 1m34s
lint / lint (pull_request) Successful in 11m0s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:58:11 +03:00
2d5acaca2f Merge pull request 'server: add GracefulTimeout option' (#304) from graceful into v3
Reviewed-on: #304
2024-02-29 23:24:43 +03:00
0674df3d9f update workflow
Some checks failed
pr / test (pull_request) Failing after 1m40s
lint / lint (pull_request) Successful in 11m5s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:23:51 +03:00
2c282825ce fixup
Some checks failed
pr / test (pull_request) Failing after 1m38s
lint / lint (pull_request) Failing after 1m47s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:17:36 +03:00
e87ff942bb bump gomod
Some checks failed
lint / lint (pull_request) Failing after 1m40s
pr / test (pull_request) Failing after 1m44s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 23:03:39 +03:00
0459ea0613 fixup
Some checks failed
lint / lint (pull_request) Failing after 1m38s
pr / test (pull_request) Failing after 1m38s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-29 22:40:05 +03:00
d44a75d074 add gracefultimeout in server 2024-02-29 22:35:55 +03:00
Кирилл Горбунов
ccf92eb84d As for interface casting
Co-authored-by: Gorbunov Kirill Andreevich <kgorbunov@mtsbank.ru>
Reviewed-on: #299
Co-authored-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
Co-committed-by: Кирилл Горбунов <kirya_gorbunov_2015@mail.ru>
2024-02-27 23:35:49 +03:00
6baf1f2744 Merge pull request 'logger/slog: fixup race condition' (#292) from log into v3
Reviewed-on: #292
2024-02-22 08:58:40 +03:00
8e2eafde9c logger/slog: fixup race condition
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-22 08:57:21 +03:00
c2b97b0f20 fixup logger/slog
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-15 10:01:41 +03:00
1db017d966 Merge pull request 'logger/slog: fixup old format' (#291) from fixupslog into v3
Reviewed-on: #291
2024-02-08 08:44:23 +03:00
debf8cb03d logger/slog: fixup old format
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-08 08:43:53 +03:00
22 changed files with 626 additions and 362 deletions

View File

@@ -10,15 +10,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.21
- name: checkout
uses: https://gitea.com/actions/checkout@v3
uses: actions/checkout@v3
- name: deps
run: go get -v -d ./...
- name: lint
uses: https://github.com/golangci/golangci-lint-action@v3.4.0
continue-on-error: true
with:
version: v1.52
version: v1.52

View File

@@ -10,14 +10,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: https://gitea.com/actions/checkout@v3
uses: actions/checkout@v3
- name: setup-go
uses: https://gitea.com/actions/setup-go@v3
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.21
- name: deps
run: go get -v -t -d ./...
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./...
run: go test -mod readonly -v ./...

View File

@@ -4,6 +4,7 @@ import (
"context"
"sync"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
maddr "go.unistack.org/micro/v3/util/addr"
@@ -15,7 +16,7 @@ import (
type memoryBroker struct {
subscribers map[string][]*memorySubscriber
addr string
opts Options
opts broker.Options
sync.RWMutex
connected bool
}
@@ -24,20 +25,20 @@ type memoryEvent struct {
err error
message interface{}
topic string
opts Options
opts broker.Options
}
type memorySubscriber struct {
ctx context.Context
exit chan bool
handler Handler
batchhandler BatchHandler
handler broker.Handler
batchhandler broker.BatchHandler
id string
topic string
opts SubscribeOptions
opts broker.SubscribeOptions
}
func (m *memoryBroker) Options() Options {
func (m *memoryBroker) Options() broker.Options {
return m.opts
}
@@ -46,6 +47,12 @@ func (m *memoryBroker) Address() string {
}
func (m *memoryBroker) Connect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock()
defer m.Unlock()
@@ -70,6 +77,12 @@ func (m *memoryBroker) Connect(ctx context.Context) error {
}
func (m *memoryBroker) Disconnect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
m.Lock()
defer m.Unlock()
@@ -81,27 +94,27 @@ func (m *memoryBroker) Disconnect(ctx context.Context) error {
return nil
}
func (m *memoryBroker) Init(opts ...Option) error {
func (m *memoryBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&m.opts)
}
return nil
}
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
msg.Header.Set(metadata.HeaderTopic, topic)
return m.publish(ctx, []*Message{msg}, opts...)
return m.publish(ctx, []*broker.Message{msg}, opts...)
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return m.publish(ctx, msgs, opts...)
}
func (m *memoryBroker) publish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
func (m *memoryBroker) publish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
m.RLock()
if !m.connected {
m.RUnlock()
return ErrNotConnected
return broker.ErrNotConnected
}
m.RUnlock()
@@ -111,9 +124,9 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*Message, opts ...Pub
case <-ctx.Done():
return ctx.Err()
default:
options := NewPublishOptions(opts...)
options := broker.NewPublishOptions(opts...)
msgTopicMap := make(map[string]Events)
msgTopicMap := make(map[string]broker.Events)
for _, v := range msgs {
p := &memoryEvent{opts: m.opts}
@@ -188,11 +201,11 @@ func (m *memoryBroker) publish(ctx context.Context, msgs []*Message, opts ...Pub
return nil
}
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, ErrNotConnected
return nil, broker.ErrNotConnected
}
m.RUnlock()
@@ -201,7 +214,7 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler
return nil, err
}
options := NewSubscribeOptions(opts...)
options := broker.NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
@@ -233,11 +246,11 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler
return sub, nil
}
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, ErrNotConnected
return nil, broker.ErrNotConnected
}
m.RUnlock()
@@ -246,7 +259,7 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
return nil, err
}
options := NewSubscribeOptions(opts...)
options := broker.NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
@@ -290,12 +303,12 @@ func (m *memoryEvent) Topic() string {
return m.topic
}
func (m *memoryEvent) Message() *Message {
func (m *memoryEvent) Message() *broker.Message {
switch v := m.message.(type) {
case *Message:
case *broker.Message:
return v
case []byte:
msg := &Message{}
msg := &broker.Message{}
if err := m.opts.Codec.Unmarshal(v, msg); err != nil {
if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, "[memory]: failed to unmarshal: %v", err)
@@ -320,7 +333,7 @@ func (m *memoryEvent) SetError(err error) {
m.err = err
}
func (m *memorySubscriber) Options() SubscribeOptions {
func (m *memorySubscriber) Options() broker.SubscribeOptions {
return m.opts
}
@@ -334,9 +347,9 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
}
// NewBroker return new memory broker
func NewBroker(opts ...Option) Broker {
func NewBroker(opts ...broker.Option) broker.Broker {
return &memoryBroker{
opts: NewOptions(opts...),
opts: broker.NewOptions(opts...),
subscribers: make(map[string][]*memorySubscriber),
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"testing"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/metadata"
)
@@ -19,7 +20,7 @@ func TestMemoryBatchBroker(t *testing.T) {
topic := "test"
count := 10
fn := func(evts Events) error {
fn := func(evts broker.Events) error {
return evts.Ack()
}
@@ -28,9 +29,9 @@ func TestMemoryBatchBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]*Message, 0, count)
msgs := make([]*broker.Message, 0, count)
for i := 0; i < count; i++ {
message := &Message{
message := &broker.Message{
Header: map[string]string{
metadata.HeaderTopic: topic,
"foo": "bar",
@@ -65,7 +66,7 @@ func TestMemoryBroker(t *testing.T) {
topic := "test"
count := 10
fn := func(p Event) error {
fn := func(p broker.Event) error {
return nil
}
@@ -74,9 +75,9 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]*Message, 0, count)
msgs := make([]*broker.Message, 0, count)
for i := 0; i < count; i++ {
message := &Message{
message := &broker.Message{
Header: map[string]string{
metadata.HeaderTopic: topic,
"foo": "bar",

82
broker/noop.go Normal file
View File

@@ -0,0 +1,82 @@
package broker
import (
"context"
"strings"
)
type NoopBroker struct {
opts Options
}
func NewBroker(opts ...Option) *NoopBroker {
b := &NoopBroker{opts: NewOptions(opts...)}
return b
}
func (b *NoopBroker) Name() string {
return b.opts.Name
}
func (b *NoopBroker) String() string {
return "noop"
}
func (b *NoopBroker) Options() Options {
return b.opts
}
func (b *NoopBroker) Init(opts ...Option) error {
for _, opt := range opts {
opt(&b.opts)
}
return nil
}
func (b *NoopBroker) Connect(_ context.Context) error {
return nil
}
func (b *NoopBroker) Disconnect(_ context.Context) error {
return nil
}
func (b *NoopBroker) Address() string {
return strings.Join(b.opts.Addrs, ",")
}
func (b *NoopBroker) BatchPublish(_ context.Context, _ []*Message, _ ...PublishOption) error {
return nil
}
func (b *NoopBroker) Publish(_ context.Context, _ string, _ *Message, _ ...PublishOption) error {
return nil
}
type NoopSubscriber struct {
ctx context.Context
topic string
handler Handler
batchHandler BatchHandler
opts SubscribeOptions
}
func (b *NoopBroker) BatchSubscribe(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) {
return &NoopSubscriber{ctx: ctx, topic: topic, opts: NewSubscribeOptions(opts...), handler: handler}, nil
}
func (s *NoopSubscriber) Options() SubscribeOptions {
return s.opts
}
func (s *NoopSubscriber) Topic() string {
return s.topic
}
func (s *NoopSubscriber) Unsubscribe(ctx context.Context) error {
return nil
}

2
go.mod
View File

@@ -1,6 +1,6 @@
module go.unistack.org/micro/v3
go 1.19
go 1.20
require (
github.com/DATA-DOG/go-sqlmock v1.5.0

View File

@@ -57,7 +57,9 @@ type Logger interface {
Log(ctx context.Context, level Level, args ...interface{})
// Logf logs message with needed level
Logf(ctx context.Context, level Level, msg string, args ...interface{})
// String returns the name of logger
// Name returns broker instance name
Name() string
// String returns the type of logger
String() string
}

View File

@@ -1,138 +0,0 @@
package logger
import (
"bytes"
"context"
"log"
"testing"
)
func TestContext(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl, ok := FromContext(NewContext(ctx, l.Fields("key", "val")))
if !ok {
t.Fatal("context without logger")
}
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
nl.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestFromContextWithFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
var ok bool
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Fields("key", "val")
ctx = NewContext(ctx, nl)
l, ok = FromContext(ctx)
if !ok {
t.Fatalf("context does not have logger")
}
l.Info(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
}
}
func TestClone(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Clone(WithLevel(ErrorLevel))
if err := nl.Init(); err != nil {
t.Fatal(err)
}
nl.Info(ctx, "info message")
if len(buf.Bytes()) != 0 {
t.Fatal("message must not be logged")
}
l.Info(ctx, "info message")
if len(buf.Bytes()) == 0 {
t.Fatal("message must be logged")
}
}
func TestRedirectStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
fn := RedirectStdLogger(l, ErrorLevel)
defer fn()
log.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
lg := NewStdLogger(l, ErrorLevel)
lg.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLogger(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Trace(ctx, "trace_msg1")
l.Warn(ctx, "warn_msg1")
l.Fields("error", "test").Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}

View File

@@ -13,11 +13,15 @@ func NewLogger(opts ...Option) Logger {
return &noopLogger{opts: options}
}
func (l *noopLogger) V(lvl Level) bool {
func (l *noopLogger) V(_ Level) bool {
return false
}
func (l *noopLogger) Level(lvl Level) {
func (l *noopLogger) Level(_ Level) {
}
func (l *noopLogger) Name() string {
return l.opts.Name
}
func (l *noopLogger) Init(opts ...Option) error {
@@ -35,7 +39,7 @@ func (l *noopLogger) Clone(opts ...Option) Logger {
return nl
}
func (l *noopLogger) Fields(attrs ...interface{}) Logger {
func (l *noopLogger) Fields(_ ...interface{}) Logger {
return l
}

View File

@@ -6,7 +6,7 @@ import (
"os"
)
// Option func
// Option func signature
type Option func(*Options)
// Options holds logger options
@@ -15,14 +15,16 @@ type Options struct {
Out io.Writer
// Context holds exernal options
Context context.Context
// Fields holds additional metadata
Fields []interface{}
// Name holds the logger name
Name string
// The logging level the logger should log
Level Level
// Fields holds additional metadata
Fields []interface{}
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// Stacktrace controls writing of stacktaces on error
Stacktrace bool
// The logging level the logger should log
Level Level
}
// NewOptions creates new options struct
@@ -61,6 +63,13 @@ func WithOutput(out io.Writer) Option {
}
}
// WithStacktrace controls writing stacktrace on error
func WithStacktrace(v bool) Option {
return func(o *Options) {
o.Stacktrace = v
}
}
// WithCallerSkipCount set frame count to skip
func WithCallerSkipCount(c int) Option {
return func(o *Options) {

View File

@@ -5,19 +5,23 @@ import (
"fmt"
"log/slog"
"os"
"regexp"
"runtime"
"strconv"
"sync"
"time"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/tracer"
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
var (
DefaultSourceKey string = slog.SourceKey
DefaultTimeKey string = slog.TimeKey
DefaultMessageKey string = slog.MessageKey
DefaultLevelKey string = slog.LevelKey
DefaultSourceKey = slog.SourceKey
DefaultTimeKey = slog.TimeKey
DefaultMessageKey = slog.MessageKey
DefaultLevelKey = slog.LevelKey
)
var (
@@ -72,9 +76,11 @@ type slogLogger struct {
sourceKey string
timeKey string
opts logger.Options
mu sync.RWMutex
}
func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
s.mu.RLock()
options := s.opts
for _, o := range opts {
@@ -112,6 +118,8 @@ func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
handler := slog.NewJSONHandler(options.Out, handleOpt)
l.slog = slog.New(handler).With(options.Fields...)
s.mu.RUnlock()
return l
}
@@ -128,6 +136,7 @@ func (s *slogLogger) Options() logger.Options {
}
func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger {
s.mu.RLock()
nl := &slogLogger{
opts: s.opts,
levelKey: s.levelKey,
@@ -140,17 +149,20 @@ func (s *slogLogger) Fields(attrs ...interface{}) logger.Logger {
handleOpt := &slog.HandlerOptions{
ReplaceAttr: nl.renameAttr,
Level: s.leveler,
Level: nl.leveler,
AddSource: true,
}
handler := slog.NewJSONHandler(s.opts.Out, handleOpt)
nl.slog = slog.New(handler).With(attrs...)
s.mu.RUnlock()
return nl
}
func (s *slogLogger) Init(opts ...logger.Option) error {
s.mu.Lock()
for _, o := range opts {
o(&s.opts)
}
@@ -180,6 +192,8 @@ func (s *slogLogger) Init(opts ...logger.Option) error {
slog.SetDefault(s.slog)
s.mu.Unlock()
return nil
}
@@ -190,7 +204,7 @@ func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, attrs ...interfa
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -200,8 +214,8 @@ func (s *slogLogger) Logf(ctx context.Context, lvl logger.Level, msg string, att
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), loggerToSlogLevel(lvl), fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -212,7 +226,7 @@ func (s *slogLogger) Info(ctx context.Context, attrs ...interface{}) {
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -222,8 +236,8 @@ func (s *slogLogger) Infof(ctx context.Context, msg string, attrs ...interface{}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelInfo, msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), slog.LevelInfo, fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -234,7 +248,7 @@ func (s *slogLogger) Debug(ctx context.Context, attrs ...interface{}) {
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -244,8 +258,8 @@ func (s *slogLogger) Debugf(ctx context.Context, msg string, attrs ...interface{
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelDebug, msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), slog.LevelDebug, fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -256,7 +270,7 @@ func (s *slogLogger) Trace(ctx context.Context, attrs ...interface{}) {
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -266,8 +280,8 @@ func (s *slogLogger) Tracef(ctx context.Context, msg string, attrs ...interface{
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelDebug-1, msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), slog.LevelDebug-1, fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -278,7 +292,16 @@ func (s *slogLogger) Error(ctx context.Context, attrs ...interface{}) {
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
if s.opts.Stacktrace {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
r.AddAttrs(slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
r.Attrs(func(a slog.Attr) bool {
if a.Key == "error" {
if span, ok := tracer.SpanFromContext(ctx); ok {
@@ -297,8 +320,17 @@ func (s *slogLogger) Errorf(ctx context.Context, msg string, attrs ...interface{
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelError, msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), slog.LevelError, fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
if s.opts.Stacktrace {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) != 0 {
r.AddAttrs(slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
}
}
r.Attrs(func(a slog.Attr) bool {
if a.Key == "error" {
if span, ok := tracer.SpanFromContext(ctx); ok {
@@ -318,7 +350,7 @@ func (s *slogLogger) Fatal(ctx context.Context, attrs ...interface{}) {
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
_ = s.slog.Handler().Handle(ctx, r)
os.Exit(1)
}
@@ -329,8 +361,8 @@ func (s *slogLogger) Fatalf(ctx context.Context, msg string, attrs ...interface{
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelError+1, msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), slog.LevelError+1, fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
os.Exit(1)
}
@@ -342,7 +374,7 @@ func (s *slogLogger) Warn(ctx context.Context, attrs ...interface{}) {
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf("%s", attrs[0]), pcs[0])
r.Add(attrs[1:]...)
// r.Add(attrs[1:]...)
_ = s.slog.Handler().Handle(ctx, r)
}
@@ -352,11 +384,15 @@ func (s *slogLogger) Warnf(ctx context.Context, msg string, attrs ...interface{}
}
var pcs [1]uintptr
runtime.Callers(s.opts.CallerSkipCount, pcs[:]) // skip [Callers, Infof]
r := slog.NewRecord(time.Now(), slog.LevelWarn, msg, pcs[0])
r.Add(attrs...)
r := slog.NewRecord(time.Now(), slog.LevelWarn, fmt.Sprintf(msg, attrs...), pcs[0])
// r.Add(attrs...)
_ = s.slog.Handler().Handle(ctx, r)
}
func (s *slogLogger) Name() string {
return s.opts.Name
}
func (s *slogLogger) String() string {
return "slog"
}

View File

@@ -9,6 +9,20 @@ import (
"go.unistack.org/micro/v3/logger"
)
func TestError(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), logger.WithStacktrace(true))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Error(ctx, "message")
if !bytes.Contains(buf.Bytes(), []byte(`"stacktrace":"`)) {
t.Fatalf("logger stacktrace not works, buf contains: %s", buf.Bytes())
}
}
func TestContext(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)

94
micro.go Normal file
View File

@@ -0,0 +1,94 @@
package micro
import (
"reflect"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/flow"
"go.unistack.org/micro/v3/fsm"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/resolver"
"go.unistack.org/micro/v3/router"
"go.unistack.org/micro/v3/selector"
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/sync"
"go.unistack.org/micro/v3/tracer"
)
func As(b any, target any) bool {
if b == nil {
return false
}
if target == nil {
return false
}
val := reflect.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflect.Ptr || val.IsNil() {
return false
}
targetType := typ.Elem()
if targetType.Kind() != reflect.Interface {
switch {
case targetType.Implements(brokerType):
break
case targetType.Implements(loggerType):
break
case targetType.Implements(clientType):
break
case targetType.Implements(serverType):
break
case targetType.Implements(codecType):
break
case targetType.Implements(flowType):
break
case targetType.Implements(fsmType):
break
case targetType.Implements(meterType):
break
case targetType.Implements(registerType):
break
case targetType.Implements(resolverType):
break
case targetType.Implements(selectorType):
break
case targetType.Implements(storeType):
break
case targetType.Implements(syncType):
break
case targetType.Implements(serviceType):
break
case targetType.Implements(routerType):
break
default:
return false
}
}
if reflect.TypeOf(b).AssignableTo(targetType) {
val.Elem().Set(reflect.ValueOf(b))
return true
}
return false
}
var brokerType = reflect.TypeOf((*broker.Broker)(nil)).Elem()
var loggerType = reflect.TypeOf((*logger.Logger)(nil)).Elem()
var clientType = reflect.TypeOf((*client.Client)(nil)).Elem()
var serverType = reflect.TypeOf((*server.Server)(nil)).Elem()
var codecType = reflect.TypeOf((*codec.Codec)(nil)).Elem()
var flowType = reflect.TypeOf((*flow.Flow)(nil)).Elem()
var fsmType = reflect.TypeOf((*fsm.FSM)(nil)).Elem()
var meterType = reflect.TypeOf((*meter.Meter)(nil)).Elem()
var registerType = reflect.TypeOf((*register.Register)(nil)).Elem()
var resolverType = reflect.TypeOf((*resolver.Resolver)(nil)).Elem()
var routerType = reflect.TypeOf((*router.Router)(nil)).Elem()
var selectorType = reflect.TypeOf((*selector.Selector)(nil)).Elem()
var storeType = reflect.TypeOf((*store.Store)(nil)).Elem()
var syncType = reflect.TypeOf((*sync.Sync)(nil)).Elem()
var tracerType = reflect.TypeOf((*tracer.Tracer)(nil)).Elem()
var serviceType = reflect.TypeOf((*Service)(nil)).Elem()

115
micro_test.go Normal file
View File

@@ -0,0 +1,115 @@
package micro
import (
"context"
"fmt"
"reflect"
"testing"
"go.unistack.org/micro/v3/broker"
"go.unistack.org/micro/v3/fsm"
)
func TestAs(t *testing.T) {
var b *bro
broTarget := &bro{name: "kafka"}
fsmTarget := &fsmT{name: "fsm"}
testCases := []struct {
b any
target any
match bool
want any
}{
{
broTarget,
&b,
true,
broTarget,
},
{
nil,
&b,
false,
nil,
},
{
fsmTarget,
&b,
false,
nil,
},
}
for i, tc := range testCases {
name := fmt.Sprintf("%d:As(Errorf(..., %v), %v)", i, tc.b, tc.target)
// Clear the target pointer, in case it was set in a previous test.
rtarget := reflect.ValueOf(tc.target)
rtarget.Elem().Set(reflect.Zero(reflect.TypeOf(tc.target).Elem()))
t.Run(name, func(t *testing.T) {
match := As(tc.b, tc.target)
if match != tc.match {
t.Fatalf("match: got %v; want %v", match, tc.match)
}
if !match {
return
}
if got := rtarget.Elem().Interface(); got != tc.want {
t.Fatalf("got %#v, want %#v", got, tc.want)
}
})
}
}
type bro struct {
name string
}
func (p *bro) Name() string { return p.name }
func (p *bro) Init(opts ...broker.Option) error { return nil }
// Options returns broker options
func (p *bro) Options() broker.Options { return broker.Options{} }
// Address return configured address
func (p *bro) Address() string { return "" }
// Connect connects to broker
func (p *bro) Connect(ctx context.Context) error { return nil }
// Disconnect disconnect from broker
func (p *bro) Disconnect(ctx context.Context) error { return nil }
// Publish message, msg can be single broker.Message or []broker.Message
func (p *bro) Publish(ctx context.Context, topic string, msg *broker.Message, opts ...broker.PublishOption) error {
return nil
}
// BatchPublish messages to broker with multiple topics
func (p *bro) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
return nil
}
// BatchSubscribe subscribes to topic messages via handler
func (p *bro) BatchSubscribe(ctx context.Context, topic string, h broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil
}
// Subscribe subscribes to topic message via handler
func (p *bro) Subscribe(ctx context.Context, topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
return nil, nil
}
// String type of broker
func (p *bro) String() string { return p.name }
type fsmT struct {
name string
}
func (f *fsmT) Start(ctx context.Context, a interface{}, o ...Option) (interface{}, error) {
return nil, nil
}
func (f *fsmT) Current() string { return f.name }
func (f *fsmT) Reset() {}
func (f *fsmT) State(s string, sf fsm.StateFunc) {}

View File

@@ -6,6 +6,7 @@ import (
"time"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/util/id"
)
@@ -16,7 +17,7 @@ var (
type node struct {
LastSeen time.Time
*Node
*register.Node
TTL time.Duration
}
@@ -25,23 +26,23 @@ type record struct {
Version string
Metadata map[string]string
Nodes map[string]*node
Endpoints []*Endpoint
Endpoints []*register.Endpoint
}
type memory struct {
sync.RWMutex
records map[string]services
watchers map[string]*watcher
opts Options
opts register.Options
}
// services is a KV map with service name as the key and a map of records as the value
type services map[string]map[string]*record
// NewRegister returns an initialized in-memory register
func NewRegister(opts ...Option) Register {
func NewRegister(opts ...register.Option) register.Register {
r := &memory{
opts: NewOptions(opts...),
opts: register.NewOptions(opts...),
records: make(map[string]services),
watchers: make(map[string]*watcher),
}
@@ -75,7 +76,7 @@ func (m *memory) ttlPrune() {
}
}
func (m *memory) sendEvent(r *Result) {
func (m *memory) sendEvent(r *register.Result) {
m.RLock()
watchers := make([]*watcher, 0, len(m.watchers))
for _, w := range m.watchers {
@@ -106,7 +107,7 @@ func (m *memory) Disconnect(ctx context.Context) error {
return nil
}
func (m *memory) Init(opts ...Option) error {
func (m *memory) Init(opts ...register.Option) error {
for _, o := range opts {
o(&m.opts)
}
@@ -118,15 +119,15 @@ func (m *memory) Init(opts ...Option) error {
return nil
}
func (m *memory) Options() Options {
func (m *memory) Options() register.Options {
return m.opts
}
func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOption) error {
func (m *memory) Register(ctx context.Context, s *register.Service, opts ...register.RegisterOption) error {
m.Lock()
defer m.Unlock()
options := NewRegisterOptions(opts...)
options := register.NewRegisterOptions(opts...)
// get the services for this domain from the register
srvs, ok := m.records[options.Domain]
@@ -153,7 +154,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
m.opts.Logger.Debugf(m.opts.Context, "Register added new service: %s, version: %s", s.Name, s.Version)
}
m.records[options.Domain] = srvs
go m.sendEvent(&Result{Action: "create", Service: s})
go m.sendEvent(&register.Result{Action: "create", Service: s})
}
var addedNodes bool
@@ -176,7 +177,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
// add the node
srvs[s.Name][s.Version].Nodes[n.ID] = &node{
Node: &Node{
Node: &register.Node{
ID: n.ID,
Address: n.Address,
Metadata: metadata,
@@ -192,7 +193,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
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)
}
go m.sendEvent(&Result{Action: "update", Service: s})
go m.sendEvent(&register.Result{Action: "update", Service: s})
} else {
// refresh TTL and timestamp
for _, n := range s.Nodes {
@@ -208,11 +209,11 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
return nil
}
func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterOption) error {
func (m *memory) Deregister(ctx context.Context, s *register.Service, opts ...register.DeregisterOption) error {
m.Lock()
defer m.Unlock()
options := NewDeregisterOptions(opts...)
options := register.NewDeregisterOptions(opts...)
// domain is set in metadata so it can be passed to watchers
if s.Metadata == nil {
@@ -252,7 +253,7 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// is cleanup
if len(version.Nodes) > 0 {
m.records[options.Domain][s.Name][s.Version] = version
go m.sendEvent(&Result{Action: "update", Service: s})
go m.sendEvent(&register.Result{Action: "update", Service: s})
return nil
}
@@ -260,7 +261,7 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// register and exit
if len(versions) == 1 {
delete(m.records[options.Domain], s.Name)
go m.sendEvent(&Result{Action: "delete", Service: s})
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)
@@ -270,7 +271,7 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// there are other versions of the service running, so only remove this version of it
delete(m.records[options.Domain][s.Name], s.Version)
go m.sendEvent(&Result{Action: "delete", Service: s})
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)
}
@@ -278,20 +279,20 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
return nil
}
func (m *memory) LookupService(ctx context.Context, name string, opts ...LookupOption) ([]*Service, error) {
options := NewLookupOptions(opts...)
func (m *memory) LookupService(ctx context.Context, name string, opts ...register.LookupOption) ([]*register.Service, error) {
options := register.NewLookupOptions(opts...)
// if it's a wildcard domain, return from all domains
if options.Domain == WildcardDomain {
if options.Domain == register.WildcardDomain {
m.RLock()
recs := m.records
m.RUnlock()
var services []*Service
var services []*register.Service
for domain := range recs {
srvs, err := m.LookupService(ctx, name, append(opts, LookupDomain(domain))...)
if err == ErrNotFound {
srvs, err := m.LookupService(ctx, name, append(opts, register.LookupDomain(domain))...)
if err == register.ErrNotFound {
continue
} else if err != nil {
return nil, err
@@ -300,7 +301,7 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...LookupO
}
if len(services) == 0 {
return nil, ErrNotFound
return nil, register.ErrNotFound
}
return services, nil
}
@@ -311,17 +312,17 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...LookupO
// check the domain exists
services, ok := m.records[options.Domain]
if !ok {
return nil, ErrNotFound
return nil, register.ErrNotFound
}
// check the service exists
versions, ok := services[name]
if !ok || len(versions) == 0 {
return nil, ErrNotFound
return nil, register.ErrNotFound
}
// serialize the response
result := make([]*Service, len(versions))
result := make([]*register.Service, len(versions))
var i int
@@ -333,19 +334,19 @@ func (m *memory) LookupService(ctx context.Context, name string, opts ...LookupO
return result, nil
}
func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Service, error) {
options := NewListOptions(opts...)
func (m *memory) ListServices(ctx context.Context, opts ...register.ListOption) ([]*register.Service, error) {
options := register.NewListOptions(opts...)
// if it's a wildcard domain, list from all domains
if options.Domain == WildcardDomain {
if options.Domain == register.WildcardDomain {
m.RLock()
recs := m.records
m.RUnlock()
var services []*Service
var services []*register.Service
for domain := range recs {
srvs, err := m.ListServices(ctx, append(opts, ListDomain(domain))...)
srvs, err := m.ListServices(ctx, append(opts, register.ListDomain(domain))...)
if err != nil {
return nil, err
}
@@ -361,11 +362,11 @@ func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Servi
// ensure the domain exists
services, ok := m.records[options.Domain]
if !ok {
return make([]*Service, 0), nil
return make([]*register.Service, 0), nil
}
// serialize the result, each version counts as an individual service
var result []*Service
var result []*register.Service
for _, service := range services {
for _, version := range service {
@@ -376,16 +377,16 @@ func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Servi
return result, nil
}
func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
func (m *memory) Watch(ctx context.Context, opts ...register.WatchOption) (register.Watcher, error) {
id, err := id.New()
if err != nil {
return nil, err
}
wo := NewWatchOptions(opts...)
wo := register.NewWatchOptions(opts...)
// construct the watcher
w := &watcher{
exit: make(chan bool),
res: make(chan *Result),
res: make(chan *register.Result),
id: id,
wo: wo,
}
@@ -406,13 +407,13 @@ func (m *memory) String() string {
}
type watcher struct {
res chan *Result
res chan *register.Result
exit chan bool
wo WatchOptions
wo register.WatchOptions
id string
}
func (m *watcher) Next() (*Result, error) {
func (m *watcher) Next() (*register.Result, error) {
for {
select {
case r := <-m.res:
@@ -429,15 +430,15 @@ func (m *watcher) Next() (*Result, error) {
if r.Service.Metadata != nil && len(r.Service.Metadata["domain"]) > 0 {
domain = r.Service.Metadata["domain"]
} else {
domain = DefaultDomain
domain = register.DefaultDomain
}
// only send the event if watching the wildcard or this specific domain
if m.wo.Domain == WildcardDomain || m.wo.Domain == domain {
if m.wo.Domain == register.WildcardDomain || m.wo.Domain == domain {
return r, nil
}
case <-m.exit:
return nil, ErrWatcherStopped
return nil, register.ErrWatcherStopped
}
}
}
@@ -451,7 +452,7 @@ func (m *watcher) Stop() {
}
}
func serviceToRecord(s *Service, ttl time.Duration) *record {
func serviceToRecord(s *register.Service, ttl time.Duration) *record {
metadata := make(map[string]string, len(s.Metadata))
for k, v := range s.Metadata {
metadata[k] = v
@@ -466,7 +467,7 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
}
}
endpoints := make([]*Endpoint, len(s.Endpoints))
endpoints := make([]*register.Endpoint, len(s.Endpoints))
for i, e := range s.Endpoints {
endpoints[i] = e
}
@@ -480,7 +481,7 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
}
}
func recordToService(r *record, domain string) *Service {
func recordToService(r *record, domain string) *register.Service {
metadata := make(map[string]string, len(r.Metadata))
for k, v := range r.Metadata {
metadata[k] = v
@@ -489,14 +490,14 @@ func recordToService(r *record, domain string) *Service {
// set the domain in metadata so it can be determined when a wildcard query is performed
metadata["domain"] = domain
endpoints := make([]*Endpoint, len(r.Endpoints))
endpoints := make([]*register.Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints {
md := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata {
md[k] = v
}
endpoints[i] = &Endpoint{
endpoints[i] = &register.Endpoint{
Name: e.Name,
Request: e.Request,
Response: e.Response,
@@ -504,7 +505,7 @@ func recordToService(r *record, domain string) *Service {
}
}
nodes := make([]*Node, len(r.Nodes))
nodes := make([]*register.Node, len(r.Nodes))
i := 0
for _, n := range r.Nodes {
md := make(map[string]string, len(n.Metadata))
@@ -512,7 +513,7 @@ func recordToService(r *record, domain string) *Service {
md[k] = v
}
nodes[i] = &Node{
nodes[i] = &register.Node{
ID: n.ID,
Address: n.Address,
Metadata: md,
@@ -520,7 +521,7 @@ func recordToService(r *record, domain string) *Service {
i++
}
return &Service{
return &register.Service{
Name: r.Name,
Version: r.Version,
Metadata: metadata,

View File

@@ -6,14 +6,16 @@ import (
"sync"
"testing"
"time"
"go.unistack.org/micro/v3/register"
)
var testData = map[string][]*Service{
var testData = map[string][]*register.Service{
"foo": {
{
Name: "foo",
Version: "1.0.0",
Nodes: []*Node{
Nodes: []*register.Node{
{
ID: "foo-1.0.0-123",
Address: "localhost:9999",
@@ -27,7 +29,7 @@ var testData = map[string][]*Service{
{
Name: "foo",
Version: "1.0.1",
Nodes: []*Node{
Nodes: []*register.Node{
{
ID: "foo-1.0.1-321",
Address: "localhost:6666",
@@ -37,7 +39,7 @@ var testData = map[string][]*Service{
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
Nodes: []*register.Node{
{
ID: "foo-1.0.3-345",
Address: "localhost:8888",
@@ -49,7 +51,7 @@ var testData = map[string][]*Service{
{
Name: "bar",
Version: "default",
Nodes: []*Node{
Nodes: []*register.Node{
{
ID: "bar-1.0.0-123",
Address: "localhost:9999",
@@ -63,7 +65,7 @@ var testData = map[string][]*Service{
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
Nodes: []*register.Node{
{
ID: "bar-1.0.1-321",
Address: "localhost:6666",
@@ -78,7 +80,7 @@ func TestMemoryRegistry(t *testing.T) {
ctx := context.TODO()
m := NewRegister()
fn := func(k string, v []*Service) {
fn := func(k string, v []*register.Service) {
services, err := m.LookupService(ctx, k)
if err != nil {
t.Errorf("Unexpected error getting service %s: %v", k, err)
@@ -155,8 +157,8 @@ func TestMemoryRegistry(t *testing.T) {
for _, v := range testData {
for _, service := range v {
services, err := m.LookupService(ctx, service.Name)
if err != ErrNotFound {
t.Errorf("Expected error: %v, got: %v", ErrNotFound, err)
if err != register.ErrNotFound {
t.Errorf("Expected error: %v, got: %v", register.ErrNotFound, err)
}
if len(services) != 0 {
t.Errorf("Expected %d services for %s, got %d", 0, service.Name, len(services))
@@ -171,7 +173,7 @@ func TestMemoryRegistryTTL(t *testing.T) {
for _, v := range testData {
for _, service := range v {
if err := m.Register(ctx, service, RegisterTTL(time.Millisecond)); err != nil {
if err := m.Register(ctx, service, register.RegisterTTL(time.Millisecond)); err != nil {
t.Fatal(err)
}
}
@@ -200,7 +202,7 @@ func TestMemoryRegistryTTLConcurrent(t *testing.T) {
ctx := context.TODO()
for _, v := range testData {
for _, service := range v {
if err := m.Register(ctx, service, RegisterTTL(waitTime/2)); err != nil {
if err := m.Register(ctx, service, register.RegisterTTL(waitTime/2)); err != nil {
t.Fatal(err)
}
}
@@ -249,34 +251,34 @@ func TestMemoryWildcard(t *testing.T) {
m := NewRegister()
ctx := context.TODO()
testSrv := &Service{Name: "foo", Version: "1.0.0"}
testSrv := &register.Service{Name: "foo", Version: "1.0.0"}
if err := m.Register(ctx, testSrv, RegisterDomain("one")); err != nil {
if err := m.Register(ctx, testSrv, register.RegisterDomain("one")); err != nil {
t.Fatalf("Register err: %v", err)
}
if err := m.Register(ctx, testSrv, RegisterDomain("two")); err != nil {
if err := m.Register(ctx, testSrv, register.RegisterDomain("two")); err != nil {
t.Fatalf("Register err: %v", err)
}
if recs, err := m.ListServices(ctx, ListDomain("one")); err != nil {
if recs, err := m.ListServices(ctx, register.ListDomain("one")); err != nil {
t.Errorf("List err: %v", err)
} else if len(recs) != 1 {
t.Errorf("Expected 1 record, got %v", len(recs))
}
if recs, err := m.ListServices(ctx, ListDomain("*")); err != nil {
if recs, err := m.ListServices(ctx, register.ListDomain("*")); err != nil {
t.Errorf("List err: %v", err)
} else if len(recs) != 2 {
t.Errorf("Expected 2 records, got %v", len(recs))
}
if recs, err := m.LookupService(ctx, testSrv.Name, LookupDomain("one")); err != nil {
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("one")); err != nil {
t.Errorf("Lookup err: %v", err)
} else if len(recs) != 1 {
t.Errorf("Expected 1 record, got %v", len(recs))
}
if recs, err := m.LookupService(ctx, testSrv.Name, LookupDomain("*")); err != nil {
if recs, err := m.LookupService(ctx, testSrv.Name, register.LookupDomain("*")); err != nil {
t.Errorf("Lookup err: %v", err)
} else if len(recs) != 2 {
t.Errorf("Expected 2 records, got %v", len(recs))
@@ -284,7 +286,7 @@ func TestMemoryWildcard(t *testing.T) {
}
func TestWatcher(t *testing.T) {
testSrv := &Service{Name: "foo", Version: "1.0.0"}
testSrv := &register.Service{Name: "foo", Version: "1.0.0"}
ctx := context.TODO()
m := NewRegister()

72
register/noop.go Normal file
View File

@@ -0,0 +1,72 @@
package register
import "context"
type noop struct {
opts Options
}
func NewRegister(opts ...Option) Register {
return &noop{
opts: NewOptions(opts...),
}
}
func (n *noop) Name() string {
return n.opts.Name
}
func (n *noop) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
return nil
}
func (n *noop) Options() Options {
return n.opts
}
func (n *noop) Connect(ctx context.Context) error {
return nil
}
func (n *noop) Disconnect(ctx context.Context) error {
return nil
}
func (n *noop) Register(ctx context.Context, service *Service, option ...RegisterOption) error {
return nil
}
func (n *noop) Deregister(ctx context.Context, service *Service, option ...DeregisterOption) error {
return nil
}
func (n *noop) LookupService(ctx context.Context, s string, option ...LookupOption) ([]*Service, error) {
return nil, nil
}
func (n *noop) ListServices(ctx context.Context, option ...ListOption) ([]*Service, error) {
return nil, nil
}
func (n *noop) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
wOpts := NewWatchOptions(opts...)
return &watcher{wo: wOpts}, nil
}
func (n *noop) String() string {
return "noop"
}
type watcher struct {
wo WatchOptions
}
func (m *watcher) Next() (*Result, error) {
return nil, nil
}
func (m *watcher) Stop() {}

View File

@@ -86,6 +86,8 @@ type Options struct {
DeregisterAttempts int
// Hooks may contains SubscriberWrapper, HandlerWrapper or Server func wrapper
Hooks options.Hooks
// GracefulTimeout timeout for graceful stop server
GracefulTimeout time.Duration
}
// NewOptions returns new options struct with default or passed values
@@ -108,6 +110,7 @@ func NewOptions(opts ...Option) Options {
Version: DefaultVersion,
ID: id.Must(),
Namespace: DefaultNamespace,
GracefulTimeout: DefaultGracefulTimeout,
}
for _, o := range opts {
@@ -321,6 +324,14 @@ func Listener(l net.Listener) Option {
// HandlerOption func
type HandlerOption func(*HandlerOptions)
// GracefulTimeout duration
func GracefulTimeout(td time.Duration) Option {
return func(o *Options) {
o.GracefulTimeout = td
}
}
// HandlerOptions struct
type HandlerOptions struct {
// Context holds external options

View File

@@ -34,6 +34,8 @@ var (
DefaultMaxMsgRecvSize = 1024 * 1024 * 4 // 4Mb
// DefaultMaxMsgSendSize holds default max send size
DefaultMaxMsgSendSize = 1024 * 1024 * 4 // 4Mb
// DefaultGracefulTimeout default time for graceful stop
DefaultGracefulTimeout = 5 * time.Second
)
// Server is a simple micro server abstraction

View File

@@ -72,8 +72,8 @@ func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...se
}
type service struct {
sync.RWMutex
opts Options
sync.RWMutex
}
// NewService creates and returns a new Service based on the packages within.
@@ -376,71 +376,15 @@ func (s *service) Run() error {
return s.Stop()
}
func getNameIndex(n string, ifaces interface{}) int {
switch values := ifaces.(type) {
case []router.Router:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []register.Register:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []store.Store:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []tracer.Tracer:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []server.Server:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []config.Config:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []meter.Meter:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []broker.Broker:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
case []client.Client:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
/*
case []logger.Logger:
for idx, iface := range values {
if iface.Name() == n {
return idx
}
}
*/
}
type Namer interface {
Name() string
}
func getNameIndex[T Namer](n string, ifaces []T) int {
for idx, iface := range ifaces {
if iface.Name() == n {
return idx
}
}
return 0
}

View File

@@ -22,13 +22,14 @@ func TestClient(t *testing.T) {
c2 := client.NewClient(client.Name("test2"))
svc := NewService(Client(c1, c2))
if err := svc.Init(); err != nil {
t.Fatal(err)
}
x1 := svc.Client("test2")
if x1.Name() != "test2" {
t.Fatal("invalid client")
t.Fatalf("invalid client %#+v", svc.Options().Clients)
}
}
@@ -40,12 +41,11 @@ func (ti *testItem) Name() string {
return ti.name
}
func TestGetNameIndex(t *testing.T) {
item1 := &testItem{name: "first"}
item2 := &testItem{name: "second"}
items := []interface{}{item1, item2}
if idx := getNameIndex("second", items); idx != 1 {
t.Fatalf("getNameIndex func error, item not found")
func Test_getNameIndex(t *testing.T) {
items := []*testItem{{name: "test1"}, {name: "test2"}}
idx := getNameIndex("test2", items)
if items[idx].Name() != "test2" {
t.Fatal("getNameIndex wrong")
}
}

View File

@@ -35,8 +35,8 @@ func TestUnmarshalJSON(t *testing.T) {
err = json.Unmarshal([]byte(`{"ttl":"1y"}`), v)
if err != nil {
t.Fatal(err)
} else if v.TTL != 31536000000000000 {
t.Fatalf("invalid duration %v != 31536000000000000", v.TTL)
} else if v.TTL != 31622400000000000 {
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
}
}
@@ -55,7 +55,7 @@ func TestParseDuration(t *testing.T) {
if err != nil {
t.Fatalf("ParseDuration error: %v", err)
}
if td.String() != "8760h0m0s" {
t.Fatalf("ParseDuration 1y != 8760h0m0s : %s", td.String())
if td.String() != "8784h0m0s" {
t.Fatalf("ParseDuration 1y != 8784h0m0s : %s", td.String())
}
}