Compare commits
8 Commits
v4.1.10
...
0144f175f0
Author | SHA1 | Date | |
---|---|---|---|
0144f175f0 | |||
b3539a32ab | |||
|
6a7223ea4a | ||
1a1b67866a | |||
b7c98da6d1 | |||
2c21cce076 | |||
c8946dcdc8 | |||
|
d342ff2626 |
48
.github/workflows/job_sync.yml
vendored
48
.github/workflows/job_sync.yml
vendored
@@ -18,34 +18,76 @@ jobs:
|
||||
echo "machine git.unistack.org login vtolstov password ${{ secrets.TOKEN_GITEA }}" >> /root/.netrc
|
||||
echo "machine github.com login vtolstov password ${{ secrets.TOKEN_GITHUB }}" >> /root/.netrc
|
||||
|
||||
- name: check master
|
||||
id: check_master
|
||||
run: |
|
||||
src_hash=$(git ls-remote https://github.com/${GITHUB_REPOSITORY} refs/heads/master | cut -f1)
|
||||
dst_hash=$(git ls-remote ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} refs/heads/master | cut -f1)
|
||||
echo "src_hash=$src_hash"
|
||||
echo "dst_hash=$dst_hash"
|
||||
if [ "$src_hash" != "$dst_hash" ]; then
|
||||
echo "sync_needed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sync_needed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: sync master
|
||||
if: steps.check_master.outputs.sync_needed == 'true'
|
||||
run: |
|
||||
git clone --filter=blob:none --filter=tree:0 --branch master --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
||||
cd repo
|
||||
git remote add --no-tags --fetch --track master upstream https://github.com/${GITHUB_REPOSITORY}
|
||||
git merge upstream/master
|
||||
git pull --rebase upstream master
|
||||
git push upstream master --progress
|
||||
git push origin master --progress
|
||||
cd ../
|
||||
rm -rf repo
|
||||
|
||||
- name: check v3
|
||||
id: check_v3
|
||||
run: |
|
||||
src_hash=$(git ls-remote https://github.com/${GITHUB_REPOSITORY} refs/heads/v3 | cut -f1)
|
||||
dst_hash=$(git ls-remote ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} refs/heads/v3 | cut -f1)
|
||||
echo "src_hash=$src_hash"
|
||||
echo "dst_hash=$dst_hash"
|
||||
if [ "$src_hash" != "$dst_hash" ]; then
|
||||
echo "sync_needed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sync_needed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: sync v3
|
||||
if: steps.check_v3.outputs.sync_needed == 'true'
|
||||
run: |
|
||||
git clone --filter=blob:none --filter=tree:0 --branch v3 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
||||
cd repo
|
||||
git remote add --no-tags --fetch --track v3 upstream https://github.com/${GITHUB_REPOSITORY}
|
||||
git merge upstream/v3
|
||||
git pull --rebase upstream v3
|
||||
git push upstream v3 --progress
|
||||
git push origin v3 --progress
|
||||
cd ../
|
||||
rm -rf repo
|
||||
|
||||
- name: check v4
|
||||
id: check_v4
|
||||
run: |
|
||||
src_hash=$(git ls-remote https://github.com/${GITHUB_REPOSITORY} refs/heads/v4 | cut -f1)
|
||||
dst_hash=$(git ls-remote ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} refs/heads/v4 | cut -f1)
|
||||
echo "src_hash=$src_hash"
|
||||
echo "dst_hash=$dst_hash"
|
||||
if [ "$src_hash" != "$dst_hash" ]; then
|
||||
echo "sync_needed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "sync_needed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: sync v4
|
||||
if: steps.check_v4.outputs.sync_needed == 'true'
|
||||
run: |
|
||||
git clone --filter=blob:none --filter=tree:0 --branch v4 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
||||
cd repo
|
||||
git remote add --no-tags --fetch --track v4 upstream https://github.com/${GITHUB_REPOSITORY}
|
||||
git merge upstream/v4
|
||||
git pull --rebase upstream v4
|
||||
git push upstream v4 --progress
|
||||
git push origin v4 --progress
|
||||
cd ../
|
||||
|
@@ -4,18 +4,20 @@ package logger
|
||||
type Level int8
|
||||
|
||||
const (
|
||||
// TraceLevel level usually used to find bugs, very verbose
|
||||
// TraceLevel usually used to find bugs, very verbose
|
||||
TraceLevel Level = iota - 2
|
||||
// DebugLevel level used only when enabled debugging
|
||||
// DebugLevel used only when enabled debugging
|
||||
DebugLevel
|
||||
// InfoLevel level used for general info about what's going on inside the application
|
||||
// InfoLevel used for general info about what's going on inside the application
|
||||
InfoLevel
|
||||
// WarnLevel level used for non-critical entries
|
||||
// WarnLevel used for non-critical entries
|
||||
WarnLevel
|
||||
// ErrorLevel level used for errors that should definitely be noted
|
||||
// ErrorLevel used for errors that should definitely be noted
|
||||
ErrorLevel
|
||||
// FatalLevel level used for critical errors and then calls `os.Exit(1)`
|
||||
// FatalLevel used for critical errors and then calls `os.Exit(1)`
|
||||
FatalLevel
|
||||
// NoneLevel used to disable logging
|
||||
NoneLevel
|
||||
)
|
||||
|
||||
// String returns logger level string representation
|
||||
@@ -33,6 +35,8 @@ func (l Level) String() string {
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case NoneLevel:
|
||||
return "none"
|
||||
}
|
||||
return "info"
|
||||
}
|
||||
@@ -58,6 +62,8 @@ func ParseLevel(lvl string) Level {
|
||||
return ErrorLevel
|
||||
case FatalLevel.String():
|
||||
return FatalLevel
|
||||
case NoneLevel.String():
|
||||
return NoneLevel
|
||||
}
|
||||
return InfoLevel
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ var (
|
||||
warnValue = slog.StringValue("warn")
|
||||
errorValue = slog.StringValue("error")
|
||||
fatalValue = slog.StringValue("fatal")
|
||||
noneValue = slog.StringValue("none")
|
||||
)
|
||||
|
||||
type wrapper struct {
|
||||
@@ -85,6 +86,8 @@ func (s *slogLogger) renameAttr(_ []string, a slog.Attr) slog.Attr {
|
||||
a.Value = errorValue
|
||||
case lvl >= logger.FatalLevel:
|
||||
a.Value = fatalValue
|
||||
case lvl >= logger.NoneLevel:
|
||||
a.Value = noneValue
|
||||
default:
|
||||
a.Value = infoValue
|
||||
}
|
||||
@@ -316,6 +319,8 @@ func loggerToSlogLevel(level logger.Level) slog.Level {
|
||||
return slog.LevelDebug - 1
|
||||
case logger.FatalLevel:
|
||||
return slog.LevelError + 1
|
||||
case logger.NoneLevel:
|
||||
return slog.LevelError + 2
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
@@ -333,6 +338,8 @@ func slogToLoggerLevel(level slog.Level) logger.Level {
|
||||
return logger.TraceLevel
|
||||
case slog.LevelError + 1:
|
||||
return logger.FatalLevel
|
||||
case slog.LevelError + 2:
|
||||
return logger.NoneLevel
|
||||
default:
|
||||
return logger.InfoLevel
|
||||
}
|
||||
|
@@ -36,6 +36,24 @@ func TestStacktrace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoneLevel(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(logger.WithLevel(logger.NoneLevel), logger.WithOutput(buf),
|
||||
WithHandlerFunc(slog.NewTextHandler),
|
||||
logger.WithAddStacktrace(true),
|
||||
)
|
||||
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l.Error(ctx, "msg1", errors.New("err"))
|
||||
|
||||
if buf.Len() != 0 {
|
||||
t.Fatalf("logger none level not works, buf contains: %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayedBuffer(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
294
metadata/context.go
Normal file
294
metadata/context.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// In the metadata package, context and metadata are treated as immutable.
|
||||
// Deep copies of metadata are made to keep things safe and correct.
|
||||
// If a user takes a map and changes it across threads, it's their responsibility.
|
||||
//
|
||||
// 1. Incoming Context
|
||||
//
|
||||
// This context is provided by an external system and populated by the server or broker of the micro framework.
|
||||
// It should not be modified. The idea is to extract all necessary data from it,
|
||||
// validate the data, and transfer it into the current context.
|
||||
// After that, only the current context should be used throughout the code.
|
||||
//
|
||||
// 2. Current Context
|
||||
//
|
||||
// This is the context used during the execution flow.
|
||||
// You can add any needed metadata to it and pass it through your code.
|
||||
//
|
||||
// 3. Outgoing Context
|
||||
//
|
||||
// This context is for sending data to external systems.
|
||||
// You can add what you need before sending it out.
|
||||
// But it’s usually better to build and prepare this context right before making the external call,
|
||||
// instead of changing it in many places.
|
||||
//
|
||||
// Execution Flow:
|
||||
//
|
||||
// [External System]
|
||||
// ↓
|
||||
// [Incoming Context]
|
||||
// ↓
|
||||
// [Extract & Validate Metadata from Incoming Context]
|
||||
// ↓
|
||||
// [Prepare Current Context]
|
||||
// ↓
|
||||
// [Enrich Current Context]
|
||||
// ↓
|
||||
// [Business Logic]
|
||||
// ↓
|
||||
// [Prepare Outgoing Context]
|
||||
// ↓
|
||||
// [External System Call]
|
||||
|
||||
type (
|
||||
metadataCurrentKey struct{}
|
||||
metadataIncomingKey struct{}
|
||||
metadataOutgoingKey struct{}
|
||||
|
||||
rawMetadata struct {
|
||||
md Metadata
|
||||
added [][]string
|
||||
}
|
||||
)
|
||||
|
||||
// NewContext creates a new context with the provided Metadata attached.
|
||||
// The Metadata must not be modified after calling this function.
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewIncomingContext creates a new context with the provided incoming Metadata attached.
|
||||
// The Metadata must not be modified after calling this function.
|
||||
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewOutgoingContext creates a new context with the provided outgoing Metadata attached.
|
||||
// The Metadata must not be modified after calling this function.
|
||||
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// AppendContext returns a new context with the provided key-value pairs (kv)
|
||||
// merged with any existing metadata in the context. For a description of kv,
|
||||
// please refer to the Pairs documentation.
|
||||
func AppendContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// AppendOutgoingContext returns a new context with the provided key-value pairs (kv)
|
||||
// merged with any existing metadata in the context. For a description of kv,
|
||||
// please refer to the Pairs documentation.
|
||||
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// FromContext retrieves a deep copy of the metadata from the context and returns it
|
||||
// with a boolean indicating if it was found.
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustContext retrieves a deep copy of the metadata from the context and panics
|
||||
// if the metadata is not found.
|
||||
func MustContext(ctx context.Context) Metadata {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromIncomingContext retrieves a deep copy of the metadata from the context and returns it
|
||||
// with a boolean indicating if it was found.
|
||||
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromIncomingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustIncomingContext retrieves a deep copy of the metadata from the context and panics
|
||||
// if the metadata is not found.
|
||||
func MustIncomingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromOutgoingContext retrieves a deep copy of the metadata from the context and returns it
|
||||
// with a boolean indicating if it was found.
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
// MustOutgoingContext retrieves a deep copy of the metadata from the context and panics
|
||||
// if the metadata is not found.
|
||||
func MustOutgoingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// ValueFromCurrentContext retrieves a deep copy of the metadata for the given key
|
||||
// from the context, performing a case-insensitive search if needed. Returns nil if not found.
|
||||
func ValueFromCurrentContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case-insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueFromIncomingContext retrieves a deep copy of the metadata for the given key
|
||||
// from the context, performing a case-insensitive search if needed. Returns nil if not found.
|
||||
func ValueFromIncomingContext(ctx context.Context, key string) []string {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := raw.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range raw.md {
|
||||
// Case-insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueFromOutgoingContext retrieves a deep copy of the metadata for the given key
|
||||
// from the context, performing a case-insensitive search if needed. Returns nil if not found.
|
||||
func ValueFromOutgoingContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case-insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -2,18 +2,18 @@
|
||||
package metadata
|
||||
|
||||
var (
|
||||
// HeaderTopic is the header name that contains topic name
|
||||
// HeaderTopic is the header name that contains topic name.
|
||||
HeaderTopic = "Micro-Topic"
|
||||
// HeaderContentType specifies content type of message
|
||||
// HeaderContentType specifies content type of message.
|
||||
HeaderContentType = "Content-Type"
|
||||
// HeaderEndpoint specifies endpoint in service
|
||||
// HeaderEndpoint specifies endpoint in service.
|
||||
HeaderEndpoint = "Micro-Endpoint"
|
||||
// HeaderService specifies service
|
||||
// HeaderService specifies service.
|
||||
HeaderService = "Micro-Service"
|
||||
// HeaderTimeout specifies timeout of operation
|
||||
// HeaderTimeout specifies timeout of operation.
|
||||
HeaderTimeout = "Micro-Timeout"
|
||||
// HeaderAuthorization specifies Authorization header
|
||||
// HeaderAuthorization specifies Authorization header.
|
||||
HeaderAuthorization = "Authorization"
|
||||
// HeaderXRequestID specifies request id
|
||||
// HeaderXRequestID specifies request id.
|
||||
HeaderXRequestID = "X-Request-Id"
|
||||
)
|
||||
|
7
metadata/helpers.go
Normal file
7
metadata/helpers.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package metadata
|
||||
|
||||
func copyOf(v []string) []string {
|
||||
vals := make([]string, len(v))
|
||||
copy(vals, v)
|
||||
return vals
|
||||
}
|
37
metadata/iterator.go
Normal file
37
metadata/iterator.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package metadata
|
||||
|
||||
import "sort"
|
||||
|
||||
type Iterator struct {
|
||||
md Metadata
|
||||
keys []string
|
||||
cur int
|
||||
cnt int
|
||||
}
|
||||
|
||||
// Next advances the iterator to the next element.
|
||||
func (iter *Iterator) Next(k *string, v *[]string) bool {
|
||||
if iter.cur+1 > iter.cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
if k != nil && v != nil {
|
||||
*k = iter.keys[iter.cur]
|
||||
vv := iter.md[*k]
|
||||
*v = make([]string, len(vv))
|
||||
copy(*v, vv)
|
||||
iter.cur++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Iterator returns an iterator for iterating over metadata in sorted order.
|
||||
func (md Metadata) Iterator() *Iterator {
|
||||
iter := &Iterator{md: md, cnt: len(md)}
|
||||
iter.keys = make([]string, 0, iter.cnt)
|
||||
for k := range md {
|
||||
iter.keys = append(iter.keys, k)
|
||||
}
|
||||
sort.Strings(iter.keys)
|
||||
return iter
|
||||
}
|
@@ -1,21 +1,18 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// defaultMetadataSize used when need to init new Metadata
|
||||
// defaultMetadataSize is used when initializing new Metadata.
|
||||
var defaultMetadataSize = 2
|
||||
|
||||
// Metadata is a mapping from metadata keys to values. Users should use the following
|
||||
// two convenience functions New and Pairs to generate Metadata.
|
||||
// Metadata maps keys to values. Use the New, NewWithMetadata and Pairs functions to create it.
|
||||
type Metadata map[string][]string
|
||||
|
||||
// New creates an zero Metadata.
|
||||
// New creates a zero-value Metadata with the specified size.
|
||||
func New(l int) Metadata {
|
||||
if l == 0 {
|
||||
l = defaultMetadataSize
|
||||
@@ -24,7 +21,7 @@ func New(l int) Metadata {
|
||||
return md
|
||||
}
|
||||
|
||||
// NewWithMetadata creates an Metadata from a given key-value map.
|
||||
// NewWithMetadata creates a Metadata from the provided key-value map.
|
||||
func NewWithMetadata(m map[string]string) Metadata {
|
||||
md := make(Metadata, len(m))
|
||||
for key, val := range m {
|
||||
@@ -33,8 +30,7 @@ func NewWithMetadata(m map[string]string) Metadata {
|
||||
return md
|
||||
}
|
||||
|
||||
// Pairs returns an Metadata formed by the mapping of key, value ...
|
||||
// Pairs panics if len(kv) is odd.
|
||||
// Pairs returns a Metadata formed from the key-value mapping. It panics if the length of kv is odd.
|
||||
func Pairs(kv ...string) Metadata {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
|
||||
@@ -46,12 +42,19 @@ func Pairs(kv ...string) Metadata {
|
||||
return md
|
||||
}
|
||||
|
||||
// Len returns the number of items in Metadata.
|
||||
func (md Metadata) Len() int {
|
||||
return len(md)
|
||||
// Join combines multiple Metadatas into a single Metadata.
|
||||
// The order of values for each key is determined by the order in which the Metadatas are provided to Join.
|
||||
func Join(mds ...Metadata) Metadata {
|
||||
out := Metadata{}
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Copy returns a copy of Metadata.
|
||||
// Copy returns a deep copy of Metadata.
|
||||
func Copy(src Metadata) Metadata {
|
||||
out := make(Metadata, len(src))
|
||||
for k, v := range src {
|
||||
@@ -60,7 +63,7 @@ func Copy(src Metadata) Metadata {
|
||||
return out
|
||||
}
|
||||
|
||||
// Copy returns a copy of Metadata.
|
||||
// Copy returns a deep copy of Metadata.
|
||||
func (md Metadata) Copy() Metadata {
|
||||
out := make(Metadata, len(md))
|
||||
for k, v := range md {
|
||||
@@ -69,7 +72,19 @@ func (md Metadata) Copy() Metadata {
|
||||
return out
|
||||
}
|
||||
|
||||
// AsMap returns a copy of Metadata with map[string]string.
|
||||
// CopyTo performs a deep copy of Metadata to the out.
|
||||
func (md Metadata) CopyTo(out Metadata) {
|
||||
for k, v := range md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of items in Metadata.
|
||||
func (md Metadata) Len() int {
|
||||
return len(md)
|
||||
}
|
||||
|
||||
// AsMap returns a deep copy of Metadata as a map[string]string
|
||||
func (md Metadata) AsMap() map[string]string {
|
||||
out := make(map[string]string, len(md))
|
||||
for k, v := range md {
|
||||
@@ -78,8 +93,7 @@ func (md Metadata) AsMap() map[string]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// AsHTTP1 returns a copy of Metadata
|
||||
// with CanonicalMIMEHeaderKey.
|
||||
// AsHTTP1 returns a deep copy of Metadata with keys converted to canonical MIME header key format.
|
||||
func (md Metadata) AsHTTP1() map[string][]string {
|
||||
out := make(map[string][]string, len(md))
|
||||
for k, v := range md {
|
||||
@@ -88,8 +102,7 @@ func (md Metadata) AsHTTP1() map[string][]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// AsHTTP1 returns a copy of Metadata
|
||||
// with strings.ToLower.
|
||||
// AsHTTP2 returns a deep copy of Metadata with keys converted to lowercase.
|
||||
func (md Metadata) AsHTTP2() map[string][]string {
|
||||
out := make(map[string][]string, len(md))
|
||||
for k, v := range md {
|
||||
@@ -98,14 +111,10 @@ func (md Metadata) AsHTTP2() map[string][]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// CopyTo copies Metadata to out.
|
||||
func (md Metadata) CopyTo(out Metadata) {
|
||||
for k, v := range md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Get obtains the values for a given key.
|
||||
// Get retrieves the values for a given key, checking the key in three formats:
|
||||
// - exact case,
|
||||
// - lower case,
|
||||
// - canonical MIME header key format.
|
||||
func (md Metadata) Get(k string) []string {
|
||||
v, ok := md[k]
|
||||
if !ok {
|
||||
@@ -117,13 +126,12 @@ func (md Metadata) Get(k string) []string {
|
||||
return v
|
||||
}
|
||||
|
||||
// GetJoined obtains the values for a given key
|
||||
// with joined values with "," symbol
|
||||
// GetJoined retrieves the values for a given key and joins them into a single string, separated by commas.
|
||||
func (md Metadata) GetJoined(k string) string {
|
||||
return strings.Join(md.Get(k), ",")
|
||||
}
|
||||
|
||||
// Set sets the value of a given key with a slice of values.
|
||||
// Set assigns the values to the given key.
|
||||
func (md Metadata) Set(key string, vals ...string) {
|
||||
if len(vals) == 0 {
|
||||
return
|
||||
@@ -131,8 +139,7 @@ func (md Metadata) Set(key string, vals ...string) {
|
||||
md[key] = vals
|
||||
}
|
||||
|
||||
// Append adds the values to key k, not overwriting what was already stored at
|
||||
// that key.
|
||||
// Append adds values to the existing values for the given key.
|
||||
func (md Metadata) Append(key string, vals ...string) {
|
||||
if len(vals) == 0 {
|
||||
return
|
||||
@@ -140,7 +147,10 @@ func (md Metadata) Append(key string, vals ...string) {
|
||||
md[key] = append(md[key], vals...)
|
||||
}
|
||||
|
||||
// Del removes the values for a given keys k.
|
||||
// Del removes the values for the given keys k. It checks and removes the keys in the following formats:
|
||||
// - exact case,
|
||||
// - lower case,
|
||||
// - canonical MIME header key format.
|
||||
func (md Metadata) Del(k ...string) {
|
||||
for i := range k {
|
||||
delete(md, k[i])
|
||||
@@ -148,303 +158,3 @@ func (md Metadata) Del(k ...string) {
|
||||
delete(md, textproto.CanonicalMIMEHeaderKey(k[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// Join joins any number of Metadatas into a single Metadata.
|
||||
//
|
||||
// The order of values for each key is determined by the order in which the Metadatas
|
||||
// containing those values are presented to Join.
|
||||
func Join(mds ...Metadata) Metadata {
|
||||
out := Metadata{}
|
||||
for _, Metadata := range mds {
|
||||
for k, v := range Metadata {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type (
|
||||
metadataIncomingKey struct{}
|
||||
metadataOutgoingKey struct{}
|
||||
metadataCurrentKey struct{}
|
||||
)
|
||||
|
||||
// NewContext creates a new context with Metadata attached. Metadata must
|
||||
// not be modified after calling this function.
|
||||
func NewContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewIncomingContext creates a new context with incoming Metadata attached. Metadata must
|
||||
// not be modified after calling this function.
|
||||
func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// NewOutgoingContext creates a new context with outgoing Metadata attached. If used
|
||||
// in conjunction with AppendOutgoingContext, NewOutgoingContext will
|
||||
// overwrite any previously-appended metadata. Metadata must not be modified after
|
||||
// calling this function.
|
||||
func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md})
|
||||
}
|
||||
|
||||
// AppendContext returns a new context with the provided kv merged
|
||||
// with any existing metadata in the context. Please refer to the documentation
|
||||
// of Pairs for a description of kv.
|
||||
func AppendContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// AppendOutgoingContext returns a new context with the provided kv merged
|
||||
// with any existing metadata in the context. Please refer to the documentation
|
||||
// of Pairs for a description of kv.
|
||||
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: AppendOutgoingContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md, _ := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
added := make([][]string, len(md.added)+1)
|
||||
copy(added, md.added)
|
||||
kvCopy := make([]string, 0, len(kv))
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||
}
|
||||
added[len(added)-1] = kvCopy
|
||||
return context.WithValue(ctx, metadataOutgoingKey{}, rawMetadata{md: md.md, added: added})
|
||||
}
|
||||
|
||||
// FromContext returns the metadata in ctx if it exists.
|
||||
func FromContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustContext returns the metadata in ctx.
|
||||
func MustContext(ctx context.Context) Metadata {
|
||||
md, ok := FromContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// FromIncomingContext returns the incoming metadata in ctx if it exists.
|
||||
func FromIncomingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromIncomingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
// MustIncomingContext returns the incoming metadata in ctx.
|
||||
func MustIncomingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// ValueFromIncomingContext returns the metadata value corresponding to the metadata
|
||||
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
|
||||
// manner.
|
||||
func ValueFromIncomingContext(ctx context.Context, key string) []string {
|
||||
raw, ok := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := raw.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range raw.md {
|
||||
// Case insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValueFromCurrentContext returns the metadata value corresponding to the metadata
|
||||
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
|
||||
// manner.
|
||||
func ValueFromCurrentContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataCurrentKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustOutgoingContext returns the outgoing metadata in ctx.
|
||||
func MustOutgoingContext(ctx context.Context) Metadata {
|
||||
md, ok := FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
panic("missing metadata")
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// ValueFromOutgoingContext returns the metadata value corresponding to the metadata
|
||||
// key from the incoming metadata if it exists. Keys are matched in a case insensitive
|
||||
// manner.
|
||||
func ValueFromOutgoingContext(ctx context.Context, key string) []string {
|
||||
md, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := md.md[key]; ok {
|
||||
return copyOf(v)
|
||||
}
|
||||
for k, v := range md.md {
|
||||
// Case insensitive comparison: Metadata is a map, and there's no guarantee
|
||||
// that the Metadata attached to the context is created using our helper
|
||||
// functions.
|
||||
if strings.EqualFold(k, key) {
|
||||
return copyOf(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyOf(v []string) []string {
|
||||
vals := make([]string, len(v))
|
||||
copy(vals, v)
|
||||
return vals
|
||||
}
|
||||
|
||||
// FromOutgoingContext returns the outgoing metadata in ctx if it exists.
|
||||
func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
|
||||
raw, ok := ctx.Value(metadataOutgoingKey{}).(rawMetadata)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
metadataSize := len(raw.md)
|
||||
for i := range raw.added {
|
||||
metadataSize += len(raw.added[i]) / 2
|
||||
}
|
||||
|
||||
out := make(Metadata, metadataSize)
|
||||
for k, v := range raw.md {
|
||||
out[k] = copyOf(v)
|
||||
}
|
||||
for _, added := range raw.added {
|
||||
if len(added)%2 == 1 {
|
||||
panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
|
||||
}
|
||||
|
||||
for i := 0; i < len(added); i += 2 {
|
||||
out[added[i]] = append(out[added[i]], added[i+1])
|
||||
}
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
type rawMetadata struct {
|
||||
md Metadata
|
||||
added [][]string
|
||||
}
|
||||
|
||||
// Iterator used to iterate over metadata with order
|
||||
type Iterator struct {
|
||||
md Metadata
|
||||
keys []string
|
||||
cur int
|
||||
cnt int
|
||||
}
|
||||
|
||||
// Next advance iterator to next element
|
||||
func (iter *Iterator) Next(k *string, v *[]string) bool {
|
||||
if iter.cur+1 > iter.cnt {
|
||||
return false
|
||||
}
|
||||
|
||||
if k != nil && v != nil {
|
||||
*k = iter.keys[iter.cur]
|
||||
vv := iter.md[*k]
|
||||
*v = make([]string, len(vv))
|
||||
copy(*v, vv)
|
||||
iter.cur++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Iterator returns the itarator for metadata in sorted order
|
||||
func (md Metadata) Iterator() *Iterator {
|
||||
iter := &Iterator{md: md, cnt: len(md)}
|
||||
iter.keys = make([]string, 0, iter.cnt)
|
||||
for k := range md {
|
||||
iter.keys = append(iter.keys, k)
|
||||
}
|
||||
sort.Strings(iter.keys)
|
||||
return iter
|
||||
}
|
||||
|
@@ -69,7 +69,8 @@ type Service struct {
|
||||
type Node struct {
|
||||
Metadata metadata.Metadata `json:"metadata,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
// Address also prefixed with scheme like grpc://xx.xx.xx.xx:1234
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
|
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/util/id"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var _ Tracer = (*noopTracer)(nil)
|
||||
@@ -18,6 +18,8 @@ func (t *noopTracer) Spans() []Span {
|
||||
return t.spans
|
||||
}
|
||||
|
||||
var uuidNil = uuid.Nil.String()
|
||||
|
||||
func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span) {
|
||||
options := NewSpanOptions(opts...)
|
||||
span := &noopSpan{
|
||||
@@ -28,8 +30,8 @@ func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption)
|
||||
labels: options.Labels,
|
||||
kind: options.Kind,
|
||||
}
|
||||
span.spanID.s, _ = id.New()
|
||||
span.traceID.s, _ = id.New()
|
||||
span.spanID.s = uuidNil
|
||||
span.traceID.s = uuidNil
|
||||
if span.ctx == nil {
|
||||
span.ctx = context.Background()
|
||||
}
|
||||
|
Reference in New Issue
Block a user