Compare commits

...

23 Commits

Author SHA1 Message Date
3be0566550 Merge pull request 'issue_296' (#300) from devstigneev/micro:issue_296 into master
Some checks failed
/ autoupdate (push) Failing after 1m21s
Reviewed-on: #300
2024-02-28 23:52:30 +03:00
e6feca2fb1 fixup
Some checks failed
pr / test (pull_request) Failing after 2m44s
lint / lint (pull_request) Successful in 11m1s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-02-28 23:50:38 +03:00
37fa3d6696 Merge remote-tracking branch 'origin/issue_296' into issue_296
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
# Conflicts:
#	options/options.go
2024-02-28 17:02:17 +03:00
47940a5ca2 add option metadata 2024-02-28 17:01:59 +03:00
d667bbee0c add option metadata 2024-02-28 15:06:29 +03:00
96dd5d869a Обновить .gitea/workflows/pr.yml
Some checks failed
/ autoupdate (push) Failing after 1m3s
2024-02-28 00:24:02 +03:00
ea90948315 Обновить .gitea/workflows/autoupdate.yml
Some checks failed
/ autoupdate (push) Failing after 1m9s
2024-02-28 00:17:09 +03:00
a382ea7d45 Merge pull request '#97 add As for Broker.' (#298) from kgorbunov/micro:#97 into master
Some checks failed
/ autoupdate (push) Failing after 1m0s
Reviewed-on: #298
2024-02-27 22:49:03 +03:00
Gorbunov Kirill Andreevich
cdfeaa7e20 #97 add As for all interface.
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-27 22:36:18 +03:00
Gorbunov Kirill Andreevich
c09674ae92 #97 add As for all interface.
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-27 22:11:39 +03:00
Gorbunov Kirill Andreevich
0d497ca0df #97 add As for Broker.
Some checks failed
lint / lint (pull_request) Has been cancelled
pr / test (pull_request) Has been cancelled
2024-02-27 21:05:42 +03:00
bb0c415a77 Merge pull request 'config: add conditions' (#287) from cond-config into master
Some checks failed
/ autoupdate (push) Failing after 1m4s
Reviewed-on: #287
2024-01-15 00:51:30 +03:00
8182cb008a config: add conditions
Some checks failed
lint / lint (pull_request) Successful in 1m27s
pr / test (pull_request) Failing after 1m13s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-01-15 00:49:27 +03:00
0771fa0647 fixup multiple client handling (#281)
Some checks failed
/ autoupdate (push) Failing after 1m28s
Reviewed-on: #281
2023-11-13 08:23:22 +03:00
b1fd82adf8 Merge pull request 'database: add FormatDSN' (#277) from database-new into master
Some checks failed
/ autoupdate (push) Failing after 1m26s
Reviewed-on: #277
2023-11-02 01:35:12 +03:00
4bc0e25017 database: add FormatDSN
Some checks failed
lint / lint (pull_request) Failing after 1m28s
pr / test (pull_request) Failing after 2m37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-02 01:31:26 +03:00
aec552aa0b Merge pull request 'database: initial import for dsn parsing' (#275) from database into master
Some checks failed
/ autoupdate (push) Failing after 1m28s
Reviewed-on: #275
2023-11-01 23:44:01 +03:00
cc81bed81d cleanup
Some checks failed
lint / lint (pull_request) Failing after 1m29s
pr / test (pull_request) Failing after 2m37s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-01 23:42:07 +03:00
7fde39fba5 database: initial import for dsn parsing
Some checks failed
lint / lint (pull_request) Failing after 1m29s
pr / test (pull_request) Failing after 2m40s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-11-01 23:40:39 +03:00
79438f11e0 logger: slog break import cycle
All checks were successful
/ autoupdate (push) Successful in 1m13s
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:48:50 +03:00
8d19abfebd small fixes
Some checks are pending
/ autoupdate (push) Has started running
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:11:35 +03:00
77f3731329 change dep
Some checks are pending
/ autoupdate (push) Waiting to run
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2023-10-17 01:10:52 +03:00
dbcf6bb74a logger: use key names from opts (#271)
All checks were successful
/ autoupdate (push) Successful in 1m14s
Reviewed-on: #271
2023-10-17 01:05:55 +03:00
19 changed files with 723 additions and 107 deletions

View File

@@ -5,8 +5,8 @@ on:
- 'master'
- 'v3'
schedule:
- cron: '* * * * *'
#- cron: '@hourly'
#- cron: '* * * * *'
- cron: '@hourly'
jobs:
autoupdate:

View File

@@ -20,4 +20,4 @@ jobs:
- name: test
env:
INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./...
run: go test -v -mod readonly -race -coverprofile=coverage.txt -covermode=atomic ./...

View File

@@ -7,8 +7,8 @@ import (
"strings"
"time"
"github.com/google/uuid"
"dario.cat/mergo"
"github.com/google/uuid"
"go.unistack.org/micro/v4/options"
mid "go.unistack.org/micro/v4/util/id"
rutil "go.unistack.org/micro/v4/util/reflect"
@@ -40,6 +40,10 @@ func (c *defaultConfig) Init(opts ...options.Option) error {
}
func (c *defaultConfig) Load(ctx context.Context, opts ...options.Option) error {
if c.opts.SkipLoad != nil && c.opts.SkipLoad(ctx, c) {
return nil
}
if err := DefaultBeforeLoad(ctx, c); err != nil && !c.opts.AllowFail {
return err
}
@@ -292,7 +296,11 @@ func fillValues(valueOf reflect.Value, tname string) error {
return nil
}
func (c *defaultConfig) Save(ctx context.Context, opts ...options.Option) error {
func (c *defaultConfig) Save(ctx context.Context, _ ...options.Option) error {
if c.opts.SkipSave != nil && c.opts.SkipSave(ctx, c) {
return nil
}
if err := DefaultBeforeSave(ctx, c); err != nil {
return err
}

View File

@@ -43,6 +43,10 @@ type Options struct {
AfterInit []func(context.Context, Config) error
// AllowFail flag to allow fail in config source
AllowFail bool
// SkipLoad runs only if condition returns true
SkipLoad func(context.Context, Config) bool
// SkipSave runs only if condition returns true
SkipSave func(context.Context, Config) bool
}
// NewOptions new options struct with filed values

157
database/dsn.go Normal file
View File

@@ -0,0 +1,157 @@
package database
import (
"crypto/tls"
"errors"
"fmt"
"net/url"
"strings"
)
var (
ErrInvalidDSNAddr = errors.New("invalid dsn addr")
ErrInvalidDSNUnescaped = errors.New("dsn must be escaped")
ErrInvalidDSNNoSlash = errors.New("dsn must contains slash")
)
type Config struct {
TLSConfig *tls.Config
Username string
Password string
Scheme string
Host string
Port string
Database string
Params []string
}
func (cfg *Config) FormatDSN() string {
var s strings.Builder
if len(cfg.Scheme) > 0 {
s.WriteString(cfg.Scheme + "://")
}
// [username[:password]@]
if len(cfg.Username) > 0 {
s.WriteString(cfg.Username)
if len(cfg.Password) > 0 {
s.WriteByte(':')
s.WriteString(url.PathEscape(cfg.Password))
}
s.WriteByte('@')
}
// [host:port]
if len(cfg.Host) > 0 {
s.WriteString(cfg.Host)
if len(cfg.Port) > 0 {
s.WriteByte(':')
s.WriteString(cfg.Port)
}
}
// /dbname
s.WriteByte('/')
s.WriteString(url.PathEscape(cfg.Database))
for i := 0; i < len(cfg.Params); i += 2 {
if i == 0 {
s.WriteString("?")
} else {
s.WriteString("&")
}
s.WriteString(cfg.Params[i])
s.WriteString("=")
s.WriteString(cfg.Params[i+1])
}
return s.String()
}
func ParseDSN(dsn string) (*Config, error) {
cfg := &Config{}
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
// Find last '/' that goes before dbname
foundSlash := false
for i := len(dsn) - 1; i >= 0; i-- {
if dsn[i] == '/' {
foundSlash = true
var j, k int
// left part is empty if i <= 0
if i > 0 {
// Find the first ':' in dsn
for j = i; j >= 0; j-- {
if dsn[j] == ':' {
cfg.Scheme = dsn[0:j]
}
}
// [username[:password]@][host]
// Find the last '@' in dsn[:i]
for j = i; j >= 0; j-- {
if dsn[j] == '@' {
// username[:password]
// Find the second ':' in dsn[:j]
for k = 0; k < j; k++ {
if dsn[k] == ':' {
if cfg.Scheme == dsn[:k] {
continue
}
var err error
cfg.Password, err = url.PathUnescape(dsn[k+1 : j])
if err != nil {
return nil, err
}
break
}
}
cfg.Username = dsn[len(cfg.Scheme)+3 : k]
break
}
}
for k = j + 1; k < i; k++ {
if dsn[k] == ':' {
cfg.Host = dsn[j+1 : k]
cfg.Port = dsn[k+1 : i]
break
}
}
}
// dbname[?param1=value1&...&paramN=valueN]
// Find the first '?' in dsn[i+1:]
for j = i + 1; j < len(dsn); j++ {
if dsn[j] == '?' {
parts := strings.Split(dsn[j+1:], "&")
cfg.Params = make([]string, 0, len(parts)*2)
for _, p := range parts {
k, v, found := strings.Cut(p, "=")
if !found {
continue
}
cfg.Params = append(cfg.Params, k, v)
}
break
}
}
var err error
dbname := dsn[i+1 : j]
if cfg.Database, err = url.PathUnescape(dbname); err != nil {
return nil, fmt.Errorf("invalid dbname %q: %w", dbname, err)
}
break
}
}
if !foundSlash && len(dsn) > 0 {
return nil, ErrInvalidDSNNoSlash
}
return cfg, nil
}

31
database/dsn_test.go Normal file
View File

@@ -0,0 +1,31 @@
package database
import (
"net/url"
"testing"
)
func TestParseDSN(t *testing.T) {
cfg, err := ParseDSN("postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2")
if err != nil {
t.Fatal(err)
}
if cfg.Password != "p@ssword#" {
t.Fatalf("parsing error")
}
}
func TestFormatDSN(t *testing.T) {
src := "postgres://username:p@ssword#@host:12345/dbname?key1=val2&key2=val2"
cfg, err := ParseDSN(src)
if err != nil {
t.Fatal(err)
}
dst, err := url.PathUnescape(cfg.FormatDSN())
if err != nil {
t.Fatal(err)
}
if src != dst {
t.Fatalf("\n%s\n%s", src, dst)
}
}

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.Attrs("state", s.Name()))
return next(sctx, s, opts...)
}
}

View File

@@ -3,7 +3,6 @@ package logger
import (
"context"
"os"
"go.unistack.org/micro/v4/options"
)
@@ -14,10 +13,7 @@ var DefaultContextAttrFuncs []ContextAttrFunc
var (
// DefaultLogger variable
DefaultLogger = NewLogger(
WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL"))),
WithContextAttrFuncs(DefaultContextAttrFuncs...),
)
DefaultLogger = NewLogger()
// DefaultLevel used by logger
DefaultLevel = InfoLevel
// DefaultCallerSkipCount used by logger

71
logger/noop.go Normal file
View File

@@ -0,0 +1,71 @@
package logger
import (
"context"
"go.unistack.org/micro/v4/options"
)
type noopLogger struct {
opts Options
}
func NewLogger(opts ...options.Option) Logger {
options := NewOptions(opts...)
return &noopLogger{opts: options}
}
func (l *noopLogger) V(lvl Level) bool {
return false
}
func (l *noopLogger) Level(lvl Level) {
}
func (l *noopLogger) Init(opts ...options.Option) error {
for _, o := range opts {
o(&l.opts)
}
return nil
}
func (l *noopLogger) Clone(opts ...options.Option) Logger {
nl := &noopLogger{opts: l.opts}
for _, o := range opts {
o(&nl.opts)
}
return nl
}
func (l *noopLogger) Attrs(attrs ...interface{}) Logger {
return l
}
func (l *noopLogger) Options() Options {
return l.opts
}
func (l *noopLogger) String() string {
return "noop"
}
func (l *noopLogger) Log(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
}
func (l *noopLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
}

View File

@@ -2,14 +2,13 @@ package logger
import (
"context"
"fmt"
"io"
"log/slog"
"os"
"reflect"
"go.unistack.org/micro/v4/options"
rutil "go.unistack.org/micro/v4/util/reflect"
"golang.org/x/exp/slog"
)
// Options holds logger options
@@ -70,7 +69,6 @@ func WithContextAttrFuncs(fncs ...ContextAttrFunc) options.Option {
for _, l := range fncs {
cv = reflect.Append(cv, reflect.ValueOf(l))
}
fmt.Printf("EEEE %#+v\n", cv.Interface())
return options.Set(src, cv.Interface(), ".ContextAttrFuncs")
}
}

View File

@@ -1,4 +1,4 @@
package logger
package slog
import (
"context"
@@ -8,7 +8,9 @@ import (
"strconv"
"time"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/tracer"
)
var (
@@ -20,29 +22,32 @@ var (
fatalValue = slog.StringValue("fatal")
)
var renameAttr = func(_ []string, a slog.Attr) slog.Attr {
func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.SourceKey:
source := a.Value.Any().(*slog.Source)
a.Value = slog.StringValue(source.File + ":" + strconv.Itoa(source.Line))
a.Key = "caller"
a.Key = s.opts.SourceKey
case slog.TimeKey:
a.Key = "timestamp"
a.Key = s.opts.TimeKey
case slog.MessageKey:
a.Key = s.opts.MessageKey
case slog.LevelKey:
level := a.Value.Any().(slog.Level)
lvl := slogToLoggerLevel(level)
a.Key = s.opts.LevelKey
switch {
case lvl < DebugLevel:
case lvl < logger.DebugLevel:
a.Value = traceValue
case lvl < InfoLevel:
case lvl < logger.InfoLevel:
a.Value = debugValue
case lvl < WarnLevel:
case lvl < logger.WarnLevel:
a.Value = infoValue
case lvl < ErrorLevel:
case lvl < logger.ErrorLevel:
a.Value = warnValue
case lvl < FatalLevel:
case lvl < logger.FatalLevel:
a.Value = errorValue
case lvl >= FatalLevel:
case lvl >= logger.FatalLevel:
a.Value = fatalValue
default:
a.Value = infoValue
@@ -55,10 +60,10 @@ var renameAttr = func(_ []string, a slog.Attr) slog.Attr {
type slogLogger struct {
slog *slog.Logger
leveler *slog.LevelVar
opts Options
opts logger.Options
}
func (s *slogLogger) Clone(opts ...options.Option) Logger {
func (s *slogLogger) Clone(opts ...options.Option) logger.Logger {
options := s.opts
for _, o := range opts {
@@ -69,14 +74,16 @@ func (s *slogLogger) Clone(opts ...options.Option) Logger {
opts: options,
}
if slog, ok := s.opts.Context.Value(loggerKey{}).(*slog.Logger); ok {
l.slog = slog
return nil
}
/*
if slog, ok := s.opts.Context.Value(loggerKey{}).(*slog.Logger); ok {
l.slog = slog
return nil
}
*/
l.leveler = new(slog.LevelVar)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: renameAttr,
ReplaceAttr: s.renameAttr,
Level: l.leveler,
AddSource: true,
}
@@ -87,25 +94,25 @@ func (s *slogLogger) Clone(opts ...options.Option) Logger {
return l
}
func (s *slogLogger) V(level Level) bool {
func (s *slogLogger) V(level logger.Level) bool {
return s.opts.Level.Enabled(level)
}
func (s *slogLogger) Level(level Level) {
func (s *slogLogger) Level(level logger.Level) {
s.leveler.Set(loggerToSlogLevel(level))
}
func (s *slogLogger) Options() Options {
func (s *slogLogger) Options() logger.Options {
return s.opts
}
func (s *slogLogger) Attrs(attrs ...interface{}) Logger {
func (s *slogLogger) Attrs(attrs ...interface{}) logger.Logger {
nl := &slogLogger{opts: s.opts}
nl.leveler = new(slog.LevelVar)
nl.leveler.Set(s.leveler.Level())
handleOpt := &slog.HandlerOptions{
ReplaceAttr: renameAttr,
ReplaceAttr: nl.renameAttr,
Level: s.leveler,
AddSource: true,
}
@@ -118,21 +125,24 @@ func (s *slogLogger) Attrs(attrs ...interface{}) Logger {
func (s *slogLogger) Init(opts ...options.Option) error {
if len(s.opts.ContextAttrFuncs) == 0 {
s.opts.ContextAttrFuncs = DefaultContextAttrFuncs
s.opts.ContextAttrFuncs = logger.DefaultContextAttrFuncs
}
for _, o := range opts {
if err := o(&s.opts); err != nil {
return err
}
}
if slog, ok := s.opts.Context.Value(loggerKey{}).(*slog.Logger); ok {
s.slog = slog
return nil
}
/*
if slog, ok := s.opts.Context.Value(loggerKey{}).(*slog.Logger); ok {
s.slog = slog
return nil
}
*/
s.leveler = new(slog.LevelVar)
handleOpt := &slog.HandlerOptions{
ReplaceAttr: renameAttr,
ReplaceAttr: s.renameAttr,
Level: s.leveler,
AddSource: true,
}
@@ -143,7 +153,7 @@ func (s *slogLogger) Init(opts ...options.Option) error {
return nil
}
func (s *slogLogger) Log(ctx context.Context, lvl Level, msg string, attrs ...interface{}) {
func (s *slogLogger) Log(ctx context.Context, lvl logger.Level, msg string, attrs ...interface{}) {
if !s.V(lvl) {
return
}
@@ -158,7 +168,7 @@ func (s *slogLogger) Log(ctx context.Context, lvl Level, msg string, attrs ...in
}
func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(InfoLevel) {
if !s.V(logger.InfoLevel) {
return
}
var pcs [1]uintptr
@@ -172,7 +182,7 @@ func (s *slogLogger) Info(ctx context.Context, msg string, attrs ...interface{})
}
func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(InfoLevel) {
if !s.V(logger.DebugLevel) {
return
}
var pcs [1]uintptr
@@ -186,7 +196,7 @@ func (s *slogLogger) Debug(ctx context.Context, msg string, attrs ...interface{}
}
func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(InfoLevel) {
if !s.V(logger.TraceLevel) {
return
}
var pcs [1]uintptr
@@ -200,7 +210,7 @@ func (s *slogLogger) Trace(ctx context.Context, msg string, attrs ...interface{}
}
func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(InfoLevel) {
if !s.V(logger.ErrorLevel) {
return
}
var pcs [1]uintptr
@@ -210,11 +220,20 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
attrs = append(attrs, fn(ctx)...)
}
r.Add(attrs...)
r.Attrs(func(a slog.Attr) bool {
if a.Key == "error" {
if span, ok := tracer.SpanFromContext(ctx); ok {
span.SetStatus(tracer.SpanStatusError, a.Value.String())
return false
}
}
return true
})
_ = s.slog.Handler().Handle(ctx, r)
}
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(InfoLevel) {
if !s.V(logger.FatalLevel) {
return
}
var pcs [1]uintptr
@@ -229,7 +248,7 @@ func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}
}
func (s *slogLogger) Warn(ctx context.Context, msg string, attrs ...interface{}) {
if !s.V(InfoLevel) {
if !s.V(logger.WarnLevel) {
return
}
var pcs [1]uintptr
@@ -246,43 +265,43 @@ func (s *slogLogger) String() string {
return "slog"
}
func NewLogger(opts ...options.Option) Logger {
func NewLogger(opts ...options.Option) logger.Logger {
l := &slogLogger{
opts: NewOptions(opts...),
opts: logger.NewOptions(opts...),
}
return l
}
func loggerToSlogLevel(level Level) slog.Level {
func loggerToSlogLevel(level logger.Level) slog.Level {
switch level {
case DebugLevel:
case logger.DebugLevel:
return slog.LevelDebug
case WarnLevel:
case logger.WarnLevel:
return slog.LevelWarn
case ErrorLevel:
case logger.ErrorLevel:
return slog.LevelError
case TraceLevel:
case logger.TraceLevel:
return slog.LevelDebug - 1
case FatalLevel:
case logger.FatalLevel:
return slog.LevelError + 1
default:
return slog.LevelInfo
}
}
func slogToLoggerLevel(level slog.Level) Level {
func slogToLoggerLevel(level slog.Level) logger.Level {
switch level {
case slog.LevelDebug:
return DebugLevel
return logger.DebugLevel
case slog.LevelWarn:
return WarnLevel
return logger.WarnLevel
case slog.LevelError:
return ErrorLevel
return logger.ErrorLevel
case slog.LevelDebug - 1:
return TraceLevel
return logger.TraceLevel
case slog.LevelError + 1:
return FatalLevel
return logger.FatalLevel
default:
return InfoLevel
return logger.InfoLevel
}
}

View File

@@ -1,21 +1,23 @@
package logger
package slog
import (
"bytes"
"context"
"log"
"testing"
"go.unistack.org/micro/v4/logger"
)
func TestContext(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl, ok := FromContext(NewContext(ctx, l.Attrs("key", "val")))
nl, ok := logger.FromContext(logger.NewContext(ctx, l.Attrs("key", "val")))
if !ok {
t.Fatal("context without logger")
}
@@ -28,7 +30,7 @@ func TestContext(t *testing.T) {
func TestAttrs(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
@@ -45,15 +47,15 @@ func TestFromContextWithFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
var ok bool
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Attrs("key", "val")
ctx = NewContext(ctx, nl)
ctx = logger.NewContext(ctx, nl)
l, ok = FromContext(ctx)
l, ok = logger.FromContext(ctx)
if !ok {
t.Fatalf("context does not have logger")
}
@@ -67,11 +69,11 @@ func TestFromContextWithFields(t *testing.T) {
func TestClone(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Clone(WithLevel(ErrorLevel))
nl := l.Clone(logger.WithLevel(logger.ErrorLevel))
if err := nl.Init(); err != nil {
t.Fatal(err)
}
@@ -87,11 +89,11 @@ func TestClone(t *testing.T) {
func TestRedirectStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(ErrorLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
fn := RedirectStdLogger(l, ErrorLevel)
fn := logger.RedirectStdLogger(l, logger.ErrorLevel)
defer fn()
log.Print("test")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"error"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test"`))) {
@@ -101,11 +103,11 @@ func TestRedirectStdLogger(t *testing.T) {
func TestStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
lg := NewStdLogger(l, ErrorLevel)
lg := logger.NewStdLogger(l, logger.ErrorLevel)
lg.Print("test")
if !(bytes.Contains(buf.Bytes(), []byte(`"level":"error"`)) && bytes.Contains(buf.Bytes(), []byte(`"msg":"test"`))) {
t.Fatalf("logger error, buf %s", buf.Bytes())
@@ -115,7 +117,7 @@ func TestStdLogger(t *testing.T) {
func TestLogger(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
l := NewLogger(logger.WithLevel(logger.TraceLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}

94
micro.go Normal file
View File

@@ -0,0 +1,94 @@
package micro
import (
"reflect"
"go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/client"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/flow"
"go.unistack.org/micro/v4/fsm"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/register"
"go.unistack.org/micro/v4/resolver"
"go.unistack.org/micro/v4/router"
"go.unistack.org/micro/v4/selector"
"go.unistack.org/micro/v4/server"
"go.unistack.org/micro/v4/store"
"go.unistack.org/micro/v4/sync"
"go.unistack.org/micro/v4/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()

103
micro_test.go Normal file
View File

@@ -0,0 +1,103 @@
package micro
import (
"context"
"fmt"
"reflect"
"testing"
"go.unistack.org/micro/v4/broker"
"go.unistack.org/micro/v4/fsm"
"go.unistack.org/micro/v4/options"
)
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 ...options.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, msg interface{}, opts ...options.Option) error { return nil }
// Subscribe subscribes to topic message via handler
func (p *bro) Subscribe(ctx context.Context, topic string, handler interface{}, opts ...options.Option) (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

@@ -151,9 +151,19 @@ func ContentType(ct string) Option {
}
// Metadata pass additional metadata
func Metadata(md metadata.Metadata) Option {
func Metadata(md any) Option {
result := metadata.Metadata{}
switch vt := md.(type) {
case metadata.Metadata:
result = metadata.Copy(vt)
case map[string]string:
result = metadata.Copy(vt)
case []string:
result.Set(vt...)
}
return func(src interface{}) error {
return Set(src, metadata.Copy(md), ".Metadata")
return Set(src, result, ".Metadata")
}
}

View File

@@ -1,10 +1,13 @@
package options_test
import (
"fmt"
"testing"
"go.unistack.org/micro/v4/codec"
"go.unistack.org/micro/v4/metadata"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/util/reflect"
)
func TestAddress(t *testing.T) {
@@ -84,3 +87,69 @@ func TestLabels(t *testing.T) {
t.Fatal("failed to set labels")
}
}
func TestMetadataAny(t *testing.T) {
type s struct {
Metadata metadata.Metadata
}
testCases := []struct {
Name string
Data any
Expected metadata.Metadata
}{
{
"strings_even",
[]string{"key1", "val1", "key2", "val2"},
metadata.Metadata{
"Key1": "val1",
"Key2": "val2",
},
},
{
"strings_odd",
[]string{"key1", "val1", "key2"},
metadata.Metadata{
"Key1": "val1",
},
},
{
"map",
map[string]string{
"key1": "val1",
"key2": "val2",
},
metadata.Metadata{
"Key1": "val1",
"Key2": "val2",
},
},
{
"metadata.Metadata",
metadata.Metadata{
"key1": "val1",
"key2": "val2",
},
metadata.Metadata{
"Key1": "val1",
"Key2": "val2",
},
},
}
for _, tt := range testCases {
t.Run(tt.Name, func(t *testing.T) {
src := &s{}
var opts []options.Option
opts = append(opts, options.Metadata(tt.Data))
for _, o := range opts {
if err := o(src); err != nil {
t.Fatal(err)
}
if !reflect.Equal(tt.Expected, src.Metadata) {
t.Fatal(fmt.Sprintf("expected: %v, actual: %v", tt.Expected, src.Metadata))
}
}
})
}
}

View File

@@ -372,19 +372,71 @@ func (s *service) Run() error {
return s.Stop()
}
type nameIface interface {
Name() string
}
func getNameIndex(n string, ifaces interface{}) int {
values, ok := ifaces.([]interface{})
if !ok {
return 0
}
for idx, iface := range values {
if ifc, ok := iface.(nameIface); ok && ifc.Name() == n {
return idx
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
}
}
*/
}
return 0
}

View File

@@ -17,20 +17,18 @@ import (
"go.unistack.org/micro/v4/tracer"
)
type testItem struct {
name string
}
func TestClient(t *testing.T) {
c1 := client.NewClient(options.Name("test1"))
c2 := client.NewClient(options.Name("test2"))
func (ti *testItem) Name() string {
return ti.name
}
svc := NewService(Client(c1, c2))
if err := svc.Init(); err != nil {
t.Fatal(err)
}
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")
x1 := svc.Client("test2")
if x1.Name() != "test2" {
t.Fatal("invalid client")
}
}

View File

@@ -1,4 +1,4 @@
package tracer
package tracer_test
import (
"bytes"
@@ -7,16 +7,20 @@ import (
"testing"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/logger/slog"
"go.unistack.org/micro/v4/tracer"
)
func TestLoggerWithTracer(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
if err := logger.Init(logger.WithOutput(buf)); err != nil {
logger.DefaultLogger = slog.NewLogger(logger.WithOutput(buf))
if err := logger.Init(); err != nil {
t.Fatal(err)
}
var span Span
ctx, span = DefaultTracer.Start(ctx, "test")
var span tracer.Span
ctx, span = tracer.DefaultTracer.Start(ctx, "test")
logger.Info(ctx, "msg")
if !strings.Contains(buf.String(), span.TraceID()) {
t.Fatalf("log does not contains tracer id")