Compare commits
	
		
			5 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cdb81a9ba3 | |||
| 413c6cc2f0 | |||
|  | f56bd70136 | ||
| b51b4107a8 | |||
| 2067c9de6b | 
| @@ -1,5 +1,5 @@ | |||||||
| # Micro | # Micro | ||||||
|  |  | ||||||
| [](https://opensource.org/licenses/Apache-2.0) | [](https://opensource.org/licenses/Apache-2.0) | ||||||
| [](https://pkg.go.dev/go.unistack.org/micro/v3?tab=overview) | [](https://pkg.go.dev/go.unistack.org/micro/v3?tab=overview) | ||||||
| [](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush) | [](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush) | ||||||
|   | |||||||
| @@ -99,6 +99,7 @@ func WithAddFields(fields ...interface{}) Option { | |||||||
| 					iv, iok := o.Fields[i].(string) | 					iv, iok := o.Fields[i].(string) | ||||||
| 					jv, jok := fields[j].(string) | 					jv, jok := fields[j].(string) | ||||||
| 					if iok && jok && iv == jv { | 					if iok && jok && iv == jv { | ||||||
|  | 						o.Fields[i+1] = fields[j+1] | ||||||
| 						fields = slices.Delete(fields, j, j+2) | 						fields = slices.Delete(fields, j, j+2) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|   | |||||||
| @@ -278,7 +278,7 @@ func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string, | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (s.opts.AddStacktrace || lvl == logger.FatalLevel) || (s.opts.AddStacktrace && lvl == logger.ErrorLevel) { | 	if s.opts.AddStacktrace && (lvl == logger.FatalLevel || lvl == logger.ErrorLevel) { | ||||||
| 		stackInfo := make([]byte, 1024*1024) | 		stackInfo := make([]byte, 1024*1024) | ||||||
| 		if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { | 		if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { | ||||||
| 			traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) | 			traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import ( | |||||||
| func TestStacktrace(t *testing.T) { | func TestStacktrace(t *testing.T) { | ||||||
| 	ctx := context.TODO() | 	ctx := context.TODO() | ||||||
| 	buf := bytes.NewBuffer(nil) | 	buf := bytes.NewBuffer(nil) | ||||||
| 	l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf), | 	l := NewLogger(logger.WithLevel(logger.DebugLevel), logger.WithOutput(buf), | ||||||
| 		WithHandlerFunc(slog.NewTextHandler), | 		WithHandlerFunc(slog.NewTextHandler), | ||||||
| 		logger.WithAddStacktrace(true), | 		logger.WithAddStacktrace(true), | ||||||
| 	) | 	) | ||||||
| @@ -124,7 +124,7 @@ func TestWithDedupKeysWithAddFields(t *testing.T) { | |||||||
|  |  | ||||||
| 	l.Info(ctx, "msg3") | 	l.Info(ctx, "msg3") | ||||||
|  |  | ||||||
| 	if !bytes.Contains(buf.Bytes(), []byte(`msg=msg3 key1=val1 key2=val2`)) { | 	if !bytes.Contains(buf.Bytes(), []byte(`msg=msg3 key1=val4 key2=val3`)) { | ||||||
| 		t.Fatalf("logger error not works, buf contains: %s", buf.Bytes()) | 		t.Fatalf("logger error not works, buf contains: %s", buf.Bytes()) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										78
									
								
								util/buffer/seeker_buffer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								util/buffer/seeker_buffer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | package buffer | ||||||
|  |  | ||||||
|  | import "io" | ||||||
|  |  | ||||||
|  | var _ interface { | ||||||
|  | 	io.ReadCloser | ||||||
|  | 	io.ReadSeeker | ||||||
|  | } = (*SeekerBuffer)(nil) | ||||||
|  |  | ||||||
|  | // Buffer is a ReadWriteCloser that supports seeking. It's intended to | ||||||
|  | // replicate the functionality of bytes.Buffer that I use in my projects. | ||||||
|  | // | ||||||
|  | // Note that the seeking is limited to the read marker; all writes are | ||||||
|  | // append-only. | ||||||
|  | type SeekerBuffer struct { | ||||||
|  | 	data []byte | ||||||
|  | 	pos  int64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSeekerBuffer(data []byte) *SeekerBuffer { | ||||||
|  | 	return &SeekerBuffer{ | ||||||
|  | 		data: data, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SeekerBuffer) Read(p []byte) (int, error) { | ||||||
|  | 	if b.pos >= int64(len(b.data)) { | ||||||
|  | 		return 0, io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	n := copy(p, b.data[b.pos:]) | ||||||
|  | 	b.pos += int64(n) | ||||||
|  | 	return n, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SeekerBuffer) Write(p []byte) (int, error) { | ||||||
|  | 	b.data = append(b.data, p...) | ||||||
|  | 	return len(p), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Seek sets the read pointer to pos. | ||||||
|  | func (b *SeekerBuffer) Seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	switch whence { | ||||||
|  | 	case io.SeekStart: | ||||||
|  | 		b.pos = offset | ||||||
|  | 	case io.SeekEnd: | ||||||
|  | 		b.pos = int64(len(b.data)) + offset | ||||||
|  | 	case io.SeekCurrent: | ||||||
|  | 		b.pos += offset | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return b.pos, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Rewind resets the read pointer to 0. | ||||||
|  | func (b *SeekerBuffer) Rewind() error { | ||||||
|  | 	if _, err := b.Seek(0, io.SeekStart); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close clears all the data out of the buffer and sets the read position to 0. | ||||||
|  | func (b *SeekerBuffer) Close() error { | ||||||
|  | 	b.data = nil | ||||||
|  | 	b.pos = 0 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Len returns the length of data remaining to be read. | ||||||
|  | func (b *SeekerBuffer) Len() int { | ||||||
|  | 	return len(b.data[b.pos:]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Bytes returns the underlying bytes from the current position. | ||||||
|  | func (b *SeekerBuffer) Bytes() []byte { | ||||||
|  | 	return b.data[b.pos:] | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								util/buffer/seeker_buffer_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								util/buffer/seeker_buffer_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | package buffer | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func noErrorT(t *testing.T, err error) { | ||||||
|  | 	if nil != err { | ||||||
|  | 		t.Fatalf("%s", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func boolT(t *testing.T, cond bool, s ...string) { | ||||||
|  | 	if !cond { | ||||||
|  | 		what := strings.Join(s, ", ") | ||||||
|  | 		if len(what) > 0 { | ||||||
|  | 			what = ": " + what | ||||||
|  | 		} | ||||||
|  | 		t.Fatalf("assert.Bool failed%s", what) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSeeking(t *testing.T) { | ||||||
|  | 	partA := []byte("hello, ") | ||||||
|  | 	partB := []byte("world!") | ||||||
|  |  | ||||||
|  | 	buf := NewSeekerBuffer(partA) | ||||||
|  |  | ||||||
|  | 	boolT(t, buf.Len() == len(partA), fmt.Sprintf("on init: have length %d, want length %d", buf.Len(), len(partA))) | ||||||
|  |  | ||||||
|  | 	b := make([]byte, 32) | ||||||
|  |  | ||||||
|  | 	n, err := buf.Read(b) | ||||||
|  | 	noErrorT(t, err) | ||||||
|  | 	boolT(t, buf.Len() == 0, fmt.Sprintf("after reading 1: have length %d, want length 0", buf.Len())) | ||||||
|  | 	boolT(t, n == len(partA), fmt.Sprintf("after reading 2: have length %d, want length %d", n, len(partA))) | ||||||
|  |  | ||||||
|  | 	n, err = buf.Write(partB) | ||||||
|  | 	noErrorT(t, err) | ||||||
|  | 	boolT(t, n == len(partB), fmt.Sprintf("after writing: have length %d, want length %d", n, len(partB))) | ||||||
|  |  | ||||||
|  | 	n, err = buf.Read(b) | ||||||
|  | 	noErrorT(t, err) | ||||||
|  | 	boolT(t, buf.Len() == 0, fmt.Sprintf("after rereading 1: have length %d, want length 0", buf.Len())) | ||||||
|  | 	boolT(t, n == len(partB), fmt.Sprintf("after rereading 2: have length %d, want length %d", n, len(partB))) | ||||||
|  |  | ||||||
|  | 	partsLen := len(partA) + len(partB) | ||||||
|  | 	_ = buf.Rewind() | ||||||
|  | 	boolT(t, buf.Len() == partsLen, fmt.Sprintf("after rewinding: have length %d, want length %d", buf.Len(), partsLen)) | ||||||
|  |  | ||||||
|  | 	buf.Close() | ||||||
|  | 	boolT(t, buf.Len() == 0, fmt.Sprintf("after closing, have length %d, want length 0", buf.Len())) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user