Merge pull request #178 from unistack-org/fsm
fsm: improve and convert to interface
This commit is contained in:
commit
7462b0b53c
126
fsm/default.go
Normal file
126
fsm/default.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package fsm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
body interface{}
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ State = &state{}
|
||||||
|
|
||||||
|
func (s *state) Name() string {
|
||||||
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) Body() interface{} {
|
||||||
|
return s.body
|
||||||
|
}
|
||||||
|
|
||||||
|
// fsm is a finite state machine
|
||||||
|
type fsm struct {
|
||||||
|
statesMap map[string]StateFunc
|
||||||
|
current string
|
||||||
|
statesOrder []string
|
||||||
|
opts Options
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFSM creates a new finite state machine having the specified initial state
|
||||||
|
// with specified options
|
||||||
|
func NewFSM(opts ...Option) *fsm {
|
||||||
|
return &fsm{
|
||||||
|
statesMap: map[string]StateFunc{},
|
||||||
|
opts: NewOptions(opts...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns the current state
|
||||||
|
func (f *fsm) Current() string {
|
||||||
|
f.mu.Lock()
|
||||||
|
s := f.current
|
||||||
|
f.mu.Unlock()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns the current state
|
||||||
|
func (f *fsm) Reset() {
|
||||||
|
f.mu.Lock()
|
||||||
|
f.current = f.opts.Initial
|
||||||
|
f.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// State adds state to fsm
|
||||||
|
func (f *fsm) State(state string, fn StateFunc) {
|
||||||
|
f.mu.Lock()
|
||||||
|
f.statesMap[state] = fn
|
||||||
|
f.statesOrder = append(f.statesOrder, state)
|
||||||
|
f.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs state machine with provided data
|
||||||
|
func (f *fsm) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
f.mu.Lock()
|
||||||
|
options := f.opts
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
sopts := []StateOption{StateDryRun(options.DryRun)}
|
||||||
|
|
||||||
|
cstate := options.Initial
|
||||||
|
states := make(map[string]StateFunc, len(f.statesMap))
|
||||||
|
for k, v := range f.statesMap {
|
||||||
|
states[k] = v
|
||||||
|
}
|
||||||
|
f.current = cstate
|
||||||
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
var s State
|
||||||
|
s = &state{name: cstate, body: args}
|
||||||
|
nstate := s.Name()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
fn, ok := states[nstate]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf(`state "%s" %w`, nstate, ErrInvalidState)
|
||||||
|
}
|
||||||
|
f.mu.Lock()
|
||||||
|
f.current = nstate
|
||||||
|
f.mu.Unlock()
|
||||||
|
|
||||||
|
// wrap the handler func
|
||||||
|
for i := len(options.Wrappers); i > 0; i-- {
|
||||||
|
fn = options.Wrappers[i-1](fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err = fn(ctx, s, sopts...)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return s.Body(), err
|
||||||
|
case s.Name() == StateEnd:
|
||||||
|
return s.Body(), nil
|
||||||
|
case s.Name() == "":
|
||||||
|
for idx := range f.statesOrder {
|
||||||
|
if f.statesOrder[idx] == nstate && len(f.statesOrder) > idx+1 {
|
||||||
|
nstate = f.statesOrder[idx+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
nstate = s.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
174
fsm/fsm.go
174
fsm/fsm.go
@ -3,8 +3,6 @@ package fsm // import "go.unistack.org/micro/v3/fsm"
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -12,170 +10,20 @@ var (
|
|||||||
StateEnd = "end"
|
StateEnd = "end"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options struct holding fsm options
|
type State interface {
|
||||||
type Options struct {
|
Name() string
|
||||||
// DryRun mode
|
Body() interface{}
|
||||||
DryRun bool
|
|
||||||
// Initial state
|
|
||||||
Initial string
|
|
||||||
// HooksBefore func slice runs in order before state
|
|
||||||
HooksBefore []HookBeforeFunc
|
|
||||||
// HooksAfter func slice runs in order after state
|
|
||||||
HooksAfter []HookAfterFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HookBeforeFunc func signature
|
// StateWrapper wraps the StateFunc and returns the equivalent
|
||||||
type HookBeforeFunc func(ctx context.Context, state string, args interface{})
|
type StateWrapper func(StateFunc) StateFunc
|
||||||
|
|
||||||
// HookAfterFunc func signature
|
|
||||||
type HookAfterFunc func(ctx context.Context, state string, args interface{})
|
|
||||||
|
|
||||||
// Option func signature
|
|
||||||
type Option func(*Options)
|
|
||||||
|
|
||||||
// StateOptions holds state options
|
|
||||||
type StateOptions struct {
|
|
||||||
DryRun bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateDryRun says that state executes in dry run mode
|
|
||||||
func StateDryRun(b bool) StateOption {
|
|
||||||
return func(o *StateOptions) {
|
|
||||||
o.DryRun = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateOption func signature
|
|
||||||
type StateOption func(*StateOptions)
|
|
||||||
|
|
||||||
// InitialState sets init state for state machine
|
|
||||||
func InitialState(initial string) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.Initial = initial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HookBefore provides hook func slice
|
|
||||||
func HookBefore(fns ...HookBeforeFunc) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.HooksBefore = fns
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HookAfter provides hook func slice
|
|
||||||
func HookAfter(fns ...HookAfterFunc) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.HooksAfter = fns
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateFunc called on state transition and return next step and error
|
// StateFunc called on state transition and return next step and error
|
||||||
type StateFunc func(ctx context.Context, args interface{}, opts ...StateOption) (string, interface{}, error)
|
type StateFunc func(ctx context.Context, state State, opts ...StateOption) (State, error)
|
||||||
|
|
||||||
// FSM is a finite state machine
|
type FSM interface {
|
||||||
type FSM struct {
|
Start(context.Context, interface{}, ...Option) (interface{}, error)
|
||||||
mu sync.Mutex
|
Current() string
|
||||||
statesMap map[string]StateFunc
|
Reset()
|
||||||
statesOrder []string
|
State(string, StateFunc)
|
||||||
opts *Options
|
|
||||||
current string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new finite state machine having the specified initial state
|
|
||||||
// with specified options
|
|
||||||
func New(opts ...Option) *FSM {
|
|
||||||
options := &Options{}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FSM{
|
|
||||||
statesMap: map[string]StateFunc{},
|
|
||||||
opts: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current returns the current state
|
|
||||||
func (f *FSM) Current() string {
|
|
||||||
f.mu.Lock()
|
|
||||||
defer f.mu.Unlock()
|
|
||||||
return f.current
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current returns the current state
|
|
||||||
func (f *FSM) Reset() {
|
|
||||||
f.mu.Lock()
|
|
||||||
f.current = f.opts.Initial
|
|
||||||
f.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// State adds state to fsm
|
|
||||||
func (f *FSM) State(state string, fn StateFunc) {
|
|
||||||
f.mu.Lock()
|
|
||||||
f.statesMap[state] = fn
|
|
||||||
f.statesOrder = append(f.statesOrder, state)
|
|
||||||
f.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialize fsm and check states
|
|
||||||
|
|
||||||
// Start runs state machine with provided data
|
|
||||||
func (f *FSM) Start(ctx context.Context, args interface{}, opts ...Option) (interface{}, error) {
|
|
||||||
var err error
|
|
||||||
var ok bool
|
|
||||||
var fn StateFunc
|
|
||||||
var nstate string
|
|
||||||
|
|
||||||
f.mu.Lock()
|
|
||||||
options := f.opts
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
sopts := []StateOption{StateDryRun(options.DryRun)}
|
|
||||||
|
|
||||||
cstate := options.Initial
|
|
||||||
states := make(map[string]StateFunc, len(f.statesMap))
|
|
||||||
for k, v := range f.statesMap {
|
|
||||||
states[k] = v
|
|
||||||
}
|
|
||||||
f.current = cstate
|
|
||||||
f.mu.Unlock()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
fn, ok = states[cstate]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf(`state "%s" %w`, cstate, ErrInvalidState)
|
|
||||||
}
|
|
||||||
f.mu.Lock()
|
|
||||||
f.current = cstate
|
|
||||||
f.mu.Unlock()
|
|
||||||
for _, fn := range options.HooksBefore {
|
|
||||||
fn(ctx, cstate, args)
|
|
||||||
}
|
|
||||||
nstate, args, err = fn(ctx, args, sopts...)
|
|
||||||
for _, fn := range options.HooksAfter {
|
|
||||||
fn(ctx, cstate, args)
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case err != nil:
|
|
||||||
return args, err
|
|
||||||
case nstate == StateEnd:
|
|
||||||
return args, nil
|
|
||||||
case nstate == "":
|
|
||||||
for idx := range f.statesOrder {
|
|
||||||
if f.statesOrder[idx] == cstate && len(f.statesOrder) > idx+1 {
|
|
||||||
nstate = f.statesOrder[idx+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cstate = nstate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,72 @@
|
|||||||
package fsm
|
package fsm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v3/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFSMStart(t *testing.T) {
|
func TestFSMStart(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
pfb := func(_ context.Context, state string, _ interface{}) {
|
if err := logger.DefaultLogger.Init(); err != nil {
|
||||||
fmt.Fprintf(buf, "before state %s\n", state)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
pfa := func(_ context.Context, state string, _ interface{}) {
|
|
||||||
fmt.Fprintf(buf, "after state %s\n", state)
|
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()))
|
||||||
|
return next(sctx, s, opts...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f := New(InitialState("1"), HookBefore(pfb), HookAfter(pfa))
|
|
||||||
f1 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
|
f := NewFSM(InitialState("1"), WrapState(wrapper))
|
||||||
args := req.(map[string]interface{})
|
f1 := func(sctx context.Context, s State, opts ...StateOption) (State, error) {
|
||||||
|
_, ok := logger.FromContext(sctx)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("f1 context does not have logger")
|
||||||
|
}
|
||||||
|
args := s.Body().(map[string]interface{})
|
||||||
if v, ok := args["request"].(string); !ok || v == "" {
|
if v, ok := args["request"].(string); !ok || v == "" {
|
||||||
return "", nil, fmt.Errorf("empty request")
|
return nil, fmt.Errorf("empty request")
|
||||||
}
|
}
|
||||||
return "2", map[string]interface{}{"response": "test2"}, nil
|
return &state{name: "", body: map[string]interface{}{"response": "state1"}}, nil
|
||||||
}
|
}
|
||||||
f2 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
|
f2 := func(sctx context.Context, s State, opts ...StateOption) (State, error) {
|
||||||
args := req.(map[string]interface{})
|
_, ok := logger.FromContext(sctx)
|
||||||
if v, ok := args["response"].(string); !ok || v == "" {
|
if !ok {
|
||||||
return "", nil, fmt.Errorf("empty response")
|
t.Fatal("f2 context does not have logger")
|
||||||
}
|
}
|
||||||
return "", map[string]interface{}{"response": "test"}, nil
|
args := s.Body().(map[string]interface{})
|
||||||
|
if v, ok := args["response"].(string); !ok || v == "" {
|
||||||
|
return nil, fmt.Errorf("empty response")
|
||||||
|
}
|
||||||
|
return &state{name: "", body: map[string]interface{}{"response": "state2"}}, nil
|
||||||
}
|
}
|
||||||
f3 := func(_ context.Context, req interface{}, _ ...StateOption) (string, interface{}, error) {
|
f3 := func(sctx context.Context, s State, opts ...StateOption) (State, error) {
|
||||||
args := req.(map[string]interface{})
|
_, ok := logger.FromContext(sctx)
|
||||||
if v, ok := args["response"].(string); !ok || v == "" {
|
if !ok {
|
||||||
return "", nil, fmt.Errorf("empty response")
|
t.Fatal("f3 context does not have logger")
|
||||||
}
|
}
|
||||||
return StateEnd, map[string]interface{}{"response": "test_last"}, nil
|
args := s.Body().(map[string]interface{})
|
||||||
|
if v, ok := args["response"].(string); !ok || v == "" {
|
||||||
|
return nil, fmt.Errorf("empty response")
|
||||||
|
}
|
||||||
|
return &state{name: StateEnd, body: map[string]interface{}{"response": "state3"}}, nil
|
||||||
}
|
}
|
||||||
f.State("1", f1)
|
f.State("1", f1)
|
||||||
f.State("2", f2)
|
f.State("2", f2)
|
||||||
f.State("3", f3)
|
f.State("3", f3)
|
||||||
rsp, err := f.Start(ctx, map[string]interface{}{"request": "test1"})
|
rsp, err := f.Start(ctx, map[string]interface{}{"request": "state"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
args := rsp.(map[string]interface{})
|
args := rsp.(map[string]interface{})
|
||||||
if v, ok := args["response"].(string); !ok || v == "" {
|
if v, ok := args["response"].(string); !ok || v == "" {
|
||||||
t.Fatalf("nil rsp: %#+v", args)
|
t.Fatalf("nil rsp: %#+v", args)
|
||||||
} else if v != "test_last" {
|
} else if v != "state3" {
|
||||||
t.Fatalf("invalid rsp %#+v", args)
|
t.Fatalf("invalid rsp %#+v", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Contains(buf.Bytes(), []byte(`before state 1`)) ||
|
|
||||||
!bytes.Contains(buf.Bytes(), []byte(`before state 2`)) ||
|
|
||||||
!bytes.Contains(buf.Bytes(), []byte(`after state 1`)) ||
|
|
||||||
!bytes.Contains(buf.Bytes(), []byte(`after state 2`)) ||
|
|
||||||
!bytes.Contains(buf.Bytes(), []byte(`after state 3`)) ||
|
|
||||||
!bytes.Contains(buf.Bytes(), []byte(`after state 3`)) {
|
|
||||||
t.Fatalf("fsm not works properly or hooks error, buf: %s", buf.Bytes())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
52
fsm/options.go
Normal file
52
fsm/options.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package fsm
|
||||||
|
|
||||||
|
// Options struct holding fsm options
|
||||||
|
type Options struct {
|
||||||
|
// Initial state
|
||||||
|
Initial string
|
||||||
|
// Wrappers runs before state
|
||||||
|
Wrappers []StateWrapper
|
||||||
|
// DryRun mode
|
||||||
|
DryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option func signature
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
// StateOptions holds state options
|
||||||
|
type StateOptions struct {
|
||||||
|
DryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateDryRun says that state executes in dry run mode
|
||||||
|
func StateDryRun(b bool) StateOption {
|
||||||
|
return func(o *StateOptions) {
|
||||||
|
o.DryRun = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateOption func signature
|
||||||
|
type StateOption func(*StateOptions)
|
||||||
|
|
||||||
|
// InitialState sets init state for state machine
|
||||||
|
func InitialState(initial string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Initial = initial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapState adds a state Wrapper to a list of options passed into the fsm
|
||||||
|
func WrapState(w StateWrapper) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Wrappers = append(o.Wrappers, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptions returns new Options struct filled by passed Option
|
||||||
|
func NewOptions(opts ...Option) Options {
|
||||||
|
options := Options{}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
@ -12,10 +12,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type defaultLogger struct {
|
type defaultLogger struct {
|
||||||
enc *json.Encoder
|
enc *json.Encoder
|
||||||
logFunc LogFunc
|
opts Options
|
||||||
logfFunc LogfFunc
|
|
||||||
opts Options
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,10 +25,6 @@ func (l *defaultLogger) Init(opts ...Option) error {
|
|||||||
}
|
}
|
||||||
l.enc = json.NewEncoder(l.opts.Out)
|
l.enc = json.NewEncoder(l.opts.Out)
|
||||||
// wrap the Log func
|
// wrap the Log func
|
||||||
for i := len(l.opts.Wrappers); i > 0; i-- {
|
|
||||||
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
|
|
||||||
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
|
|
||||||
}
|
|
||||||
l.Unlock()
|
l.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -47,17 +41,10 @@ func (l *defaultLogger) Clone(opts ...Option) Logger {
|
|||||||
o(&oldopts)
|
o(&oldopts)
|
||||||
}
|
}
|
||||||
|
|
||||||
oldopts.Wrappers = newopts.Wrappers
|
|
||||||
l.Lock()
|
l.Lock()
|
||||||
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc, enc: json.NewEncoder(l.opts.Out)}
|
cl := &defaultLogger{opts: oldopts, enc: json.NewEncoder(l.opts.Out)}
|
||||||
l.Unlock()
|
l.Unlock()
|
||||||
|
|
||||||
// wrap the Log func
|
|
||||||
for i := len(newopts.Wrappers); i > 0; i-- {
|
|
||||||
cl.logFunc = newopts.Wrappers[i-1].Log(cl.logFunc)
|
|
||||||
cl.logfFunc = newopts.Wrappers[i-1].Logf(cl.logfFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cl
|
return cl
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,15 +62,17 @@ func (l *defaultLogger) Level(level Level) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Fields(fields ...interface{}) Logger {
|
func (l *defaultLogger) Fields(fields ...interface{}) Logger {
|
||||||
|
l.RLock()
|
||||||
nl := &defaultLogger{opts: l.opts, enc: l.enc}
|
nl := &defaultLogger{opts: l.opts, enc: l.enc}
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
|
l.RUnlock()
|
||||||
return nl
|
return nl
|
||||||
} else if len(fields)%2 != 0 {
|
} else if len(fields)%2 != 0 {
|
||||||
fields = fields[:len(fields)-1]
|
fields = fields[:len(fields)-1]
|
||||||
}
|
}
|
||||||
nl.logFunc = l.logFunc
|
nl.opts.Fields = copyFields(l.opts.Fields)
|
||||||
nl.logfFunc = l.logfFunc
|
|
||||||
nl.opts.Fields = append(nl.opts.Fields, fields...)
|
nl.opts.Fields = append(nl.opts.Fields, fields...)
|
||||||
|
l.RUnlock()
|
||||||
return nl
|
return nl
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,27 +132,27 @@ func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
||||||
l.logfFunc(ctx, InfoLevel, msg, args...)
|
l.Logf(ctx, InfoLevel, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
||||||
l.logfFunc(ctx, ErrorLevel, msg, args...)
|
l.Logf(ctx, ErrorLevel, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
||||||
l.logfFunc(ctx, DebugLevel, msg, args...)
|
l.Logf(ctx, DebugLevel, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
||||||
l.logfFunc(ctx, WarnLevel, msg, args...)
|
l.Logf(ctx, WarnLevel, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
||||||
l.logfFunc(ctx, TraceLevel, msg, args...)
|
l.Logf(ctx, TraceLevel, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
||||||
l.logfFunc(ctx, FatalLevel, msg, args...)
|
l.Logf(ctx, FatalLevel, msg, args...)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,8 +225,6 @@ func NewLogger(opts ...Option) Logger {
|
|||||||
l := &defaultLogger{
|
l := &defaultLogger{
|
||||||
opts: NewOptions(opts...),
|
opts: NewOptions(opts...),
|
||||||
}
|
}
|
||||||
l.logFunc = l.Log
|
|
||||||
l.logfFunc = l.Logf
|
|
||||||
l.enc = json.NewEncoder(l.opts.Out)
|
l.enc = json.NewEncoder(l.opts.Out)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,33 @@ func TestFields(t *testing.T) {
|
|||||||
if err := l.Init(); err != nil {
|
if err := l.Init(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
l.Fields("key", "val").Info(ctx, "message")
|
|
||||||
|
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"`)) {
|
if !bytes.Contains(buf.Bytes(), []byte(`"key":"val"`)) {
|
||||||
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
|
t.Fatalf("logger fields not works, buf contains: %s", buf.Bytes())
|
||||||
}
|
}
|
||||||
@ -110,39 +136,3 @@ func TestLogger(t *testing.T) {
|
|||||||
t.Fatalf("logger error, buf %s", buf.Bytes())
|
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggerWrapper(t *testing.T) {
|
|
||||||
ctx := context.TODO()
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
|
||||||
if err := l.Init(WrapLogger(NewOmitWrapper())); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
type secret struct {
|
|
||||||
Name string
|
|
||||||
Passw string `logger:"omit"`
|
|
||||||
}
|
|
||||||
s := &secret{Name: "name", Passw: "secret"}
|
|
||||||
l.Errorf(ctx, "test %#+v", s)
|
|
||||||
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
|
|
||||||
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOmitLoggerWrapper(t *testing.T) {
|
|
||||||
ctx := context.TODO()
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
l := NewOmitLogger(NewLogger(WithLevel(TraceLevel), WithOutput(buf)))
|
|
||||||
if err := l.Init(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
type secret struct {
|
|
||||||
Name string
|
|
||||||
Passw string `logger:"omit"`
|
|
||||||
}
|
|
||||||
s := &secret{Name: "name", Passw: "secret"}
|
|
||||||
l.Errorf(ctx, "test %#+v", s)
|
|
||||||
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
|
|
||||||
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -19,8 +19,6 @@ type Options struct {
|
|||||||
Fields []interface{}
|
Fields []interface{}
|
||||||
// Name holds the logger name
|
// Name holds the logger name
|
||||||
Name string
|
Name string
|
||||||
// Wrappers logger wrapper that called before actual Log/Logf function
|
|
||||||
Wrappers []Wrapper
|
|
||||||
// The logging level the logger should log
|
// The logging level the logger should log
|
||||||
Level Level
|
Level Level
|
||||||
// CallerSkipCount number of frmaes to skip
|
// CallerSkipCount number of frmaes to skip
|
||||||
@ -83,10 +81,3 @@ func WithName(n string) Option {
|
|||||||
o.Name = n
|
o.Name = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapLogger adds a logger Wrapper to a list of options passed into the logger
|
|
||||||
func WrapLogger(w Wrapper) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.Wrappers = append(o.Wrappers, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,166 +0,0 @@
|
|||||||
package logger // import "go.unistack.org/micro/v3/logger/wrapper"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
rutil "go.unistack.org/micro/v3/util/reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogFunc function used for Log method
|
|
||||||
type LogFunc func(ctx context.Context, level Level, args ...interface{})
|
|
||||||
|
|
||||||
// LogfFunc function used for Logf method
|
|
||||||
type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
|
|
||||||
|
|
||||||
type Wrapper interface {
|
|
||||||
// Log logs message with needed level
|
|
||||||
Log(LogFunc) LogFunc
|
|
||||||
// Logf logs message with needed level
|
|
||||||
Logf(LogfFunc) LogfFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Logger = &omitLogger{}
|
|
||||||
|
|
||||||
type omitLogger struct {
|
|
||||||
l Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOmitLogger(l Logger) Logger {
|
|
||||||
return &omitLogger{l: l}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Init(opts ...Option) error {
|
|
||||||
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) V(level Level) bool {
|
|
||||||
return w.l.V(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Level(level Level) {
|
|
||||||
w.l.Level(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Clone(opts ...Option) Logger {
|
|
||||||
return w.l.Clone(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Options() Options {
|
|
||||||
return w.l.Options()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Fields(fields ...interface{}) Logger {
|
|
||||||
return w.l.Fields(fields...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Info(ctx context.Context, args ...interface{}) {
|
|
||||||
w.l.Info(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Trace(ctx context.Context, args ...interface{}) {
|
|
||||||
w.l.Trace(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Debug(ctx context.Context, args ...interface{}) {
|
|
||||||
w.l.Debug(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Warn(ctx context.Context, args ...interface{}) {
|
|
||||||
w.l.Warn(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Error(ctx context.Context, args ...interface{}) {
|
|
||||||
w.l.Error(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Fatal(ctx context.Context, args ...interface{}) {
|
|
||||||
w.l.Fatal(ctx, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
|
||||||
w.l.Infof(ctx, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
|
||||||
w.l.Tracef(ctx, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
|
||||||
w.l.Debugf(ctx, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
|
||||||
w.l.Warnf(ctx, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
|
||||||
w.l.Errorf(ctx, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
|
||||||
w.l.Fatalf(ctx, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
|
|
||||||
w.l.Log(ctx, level, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
|
|
||||||
w.l.Logf(ctx, level, msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitLogger) String() string {
|
|
||||||
return w.l.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type omitWrapper struct{}
|
|
||||||
|
|
||||||
func NewOmitWrapper() Wrapper {
|
|
||||||
return &omitWrapper{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArgs(args []interface{}) []interface{} {
|
|
||||||
nargs := make([]interface{}, 0, len(args))
|
|
||||||
var err error
|
|
||||||
for _, arg := range args {
|
|
||||||
val := reflect.ValueOf(arg)
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
narg := arg
|
|
||||||
if val.Kind() != reflect.Struct {
|
|
||||||
nargs = append(nargs, narg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if narg, err = rutil.Zero(arg); err != nil {
|
|
||||||
nargs = append(nargs, narg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rutil.CopyDefaults(narg, arg)
|
|
||||||
if flds, ferr := rutil.StructFields(narg); ferr == nil {
|
|
||||||
for _, fld := range flds {
|
|
||||||
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" {
|
|
||||||
fld.Value.Set(reflect.Zero(fld.Value.Type()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nargs = append(nargs, narg)
|
|
||||||
}
|
|
||||||
return nargs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitWrapper) Log(fn LogFunc) LogFunc {
|
|
||||||
return func(ctx context.Context, level Level, args ...interface{}) {
|
|
||||||
fn(ctx, level, getArgs(args)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *omitWrapper) Logf(fn LogfFunc) LogfFunc {
|
|
||||||
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
|
|
||||||
fn(ctx, level, msg, getArgs(args)...)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user