fsm: improve and convert to interface #178
							
								
								
									
										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)...) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user