Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spanlogger: Take an interface implementation for extracting tenant IDs #59

Merged
merged 1 commit into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
* [CHANGE] Added CHANGELOG.md and Pull Request template to reference the changelog
* [CHANGE] Remove `cortex_` prefix for metrics registered in the ring. #46
* [CHANGE] Rename `kv/kvtls` to `crypto/tls`. #39
* [CHANGE] spanlogger: Take interface implementation for extracting tenant ID. #59
* [ENHANCEMENT] Add middleware package. #38
* [ENHANCEMENT] Add the ring package #45
* [ENHANCEMENT] Add limiter package. #41
* [ENHANCEMENT] Add grpcclient, grpcencoding and grpcutil packages. #39
* [ENHANCEMENT] Replace go-kit/kit/log with go-kit/log. #52
* [ENHANCEMENT] Add spanlogger package. #42
* [ENHANCEMENT] Add spanlogger package. #42
* [BUGFIX] spanlogger: Support multiple tenant IDs. #59
50 changes: 20 additions & 30 deletions spanlogger/spanlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ package spanlogger

import (
"context"
"strings"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
"github.com/weaveworks/common/tracing"
"github.com/weaveworks/common/user"
)

type loggerCtxMarker struct{}

// TenantResolver provides methods for extracting tenant IDs from a context.
type TenantResolver interface {
// TenantID tries to extract a tenant ID from a context.
TenantID(context.Context) (string, error)
// TenantIDs tries to extract tenant IDs from a context.
TenantIDs(context.Context) ([]string, error)
}

const (
// TenantIDsTagName is the tenant IDs tag name.
TenantIDsTagName = "tenant_ids"
Expand All @@ -32,13 +38,13 @@ type SpanLogger struct {

// New makes a new SpanLogger with a log.Logger to send logs to. The provided context will have the logger attached
// to it and can be retrieved with FromContext.
func New(ctx context.Context, logger log.Logger, method string, kvps ...interface{}) (*SpanLogger, context.Context) {
func New(ctx context.Context, logger log.Logger, method string, resolver TenantResolver, kvps ...interface{}) (*SpanLogger, context.Context) {
span, ctx := opentracing.StartSpanFromContext(ctx, method)
if id := tenantID(ctx); id != "" {
span.SetTag(TenantIDsTagName, []string{id})
if ids, err := resolver.TenantIDs(ctx); err == nil && len(ids) > 0 {
span.SetTag(TenantIDsTagName, ids)
}
l := &SpanLogger{
Logger: log.With(withContext(ctx, logger), "method", method),
Logger: log.With(withContext(ctx, logger, resolver), "method", method),
Span: span,
}
if len(kvps) > 0 {
Expand All @@ -53,7 +59,7 @@ func New(ctx context.Context, logger log.Logger, method string, kvps ...interfac
// If there is no parent span, the SpanLogger will only log to the logger
// within the context. If the context doesn't have a logger, the fallback
// logger is used.
func FromContext(ctx context.Context, fallback log.Logger) *SpanLogger {
func FromContext(ctx context.Context, fallback log.Logger, resolver TenantResolver) *SpanLogger {
logger, ok := ctx.Value(loggerCtxKey).(log.Logger)
if !ok {
logger = fallback
Expand All @@ -63,7 +69,7 @@ func FromContext(ctx context.Context, fallback log.Logger) *SpanLogger {
sp = defaultNoopSpan
}
return &SpanLogger{
Logger: withContext(ctx, logger),
Logger: withContext(ctx, logger, resolver),
Span: sp,
}
}
Expand All @@ -90,34 +96,18 @@ func (s *SpanLogger) Error(err error) error {
return err
}

// tenantID tries to extract the tenant ID from ctx.
func tenantID(ctx context.Context) string {
//lint:ignore faillint wrapper around upstream method
id, err := user.ExtractOrgID(ctx)
if err != nil {
return ""
}

// handle the relative reference to current and parent path.
if id == "." || id == ".." || strings.ContainsAny(id, `\/`) {
return ""
}

return id
}

func withContext(ctx context.Context, l log.Logger) log.Logger {
func withContext(ctx context.Context, logger log.Logger, resolver TenantResolver) log.Logger {
// Weaveworks uses "orgs" and "orgID" to represent Cortex users,
// even though the code-base generally uses `userID` to refer to the same thing.
userID := tenantID(ctx)
if userID != "" {
l = log.With(l, "org_id", userID)
userID, err := resolver.TenantID(ctx)
if err == nil && userID != "" {
logger = log.With(logger, "org_id", userID)
}

traceID, ok := tracing.ExtractSampledTraceID(ctx)
if !ok {
return l
return logger
}

return log.With(l, "traceID", traceID)
return log.With(logger, "traceID", traceID)
}
50 changes: 41 additions & 9 deletions spanlogger/spanlogger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package spanlogger

import (
"context"
"strings"
"testing"

"github.com/go-kit/log"
Expand All @@ -14,12 +15,13 @@ import (

func TestSpanLogger_Log(t *testing.T) {
logger := log.NewNopLogger()
span, ctx := New(context.Background(), logger, "test", "bar")
resolver := fakeResolver{}
span, ctx := New(context.Background(), logger, "test", resolver, "bar")
_ = span.Log("foo")
newSpan := FromContext(ctx, logger)
newSpan := FromContext(ctx, logger, resolver)
require.Equal(t, span.Span, newSpan.Span)
_ = newSpan.Log("bar")
noSpan := FromContext(context.Background(), logger)
noSpan := FromContext(context.Background(), logger, resolver)
_ = noSpan.Log("foo")
require.Error(t, noSpan.Error(errors.New("err")))
require.NoError(t, noSpan.Error(nil))
Expand All @@ -31,13 +33,14 @@ func TestSpanLogger_CustomLogger(t *testing.T) {
logged = append(logged, keyvals)
return nil
}
span, ctx := New(context.Background(), logger, "test")
resolver := fakeResolver{}
span, ctx := New(context.Background(), logger, "test", resolver)
_ = span.Log("msg", "original spanlogger")

span = FromContext(ctx, log.NewNopLogger())
span = FromContext(ctx, log.NewNopLogger(), resolver)
_ = span.Log("msg", "restored spanlogger")

span = FromContext(context.Background(), logger)
span = FromContext(context.Background(), logger, resolver)
_ = span.Log("msg", "fallback spanlogger")

expect := [][]interface{}{
Expand All @@ -57,15 +60,15 @@ func TestSpanCreatedWithTenantTag(t *testing.T) {
func TestSpanCreatedWithoutTenantTag(t *testing.T) {
mockSpan := createSpan(context.Background())

_, exist := mockSpan.Tags()[TenantIDsTagName]
require.False(t, exist)
_, exists := mockSpan.Tags()[TenantIDsTagName]
require.False(t, exists)
}

func createSpan(ctx context.Context) *mocktracer.MockSpan {
mockTracer := mocktracer.New()
opentracing.SetGlobalTracer(mockTracer)

logger, _ := New(ctx, log.NewNopLogger(), "name")
logger, _ := New(ctx, log.NewNopLogger(), "name", fakeResolver{})
return logger.Span.(*mocktracer.MockSpan)
}

Expand All @@ -74,3 +77,32 @@ type funcLogger func(keyvals ...interface{}) error
func (f funcLogger) Log(keyvals ...interface{}) error {
return f(keyvals...)
}

type fakeResolver struct {
}

func (fakeResolver) TenantID(ctx context.Context) (string, error) {
id, err := user.ExtractOrgID(ctx)
if err != nil {
return "", err
}

// handle the relative reference to current and parent path.
if id == "." || id == ".." || strings.ContainsAny(id, `\/`) {
return "", nil
}

return id, nil
}

func (r fakeResolver) TenantIDs(ctx context.Context) ([]string, error) {
id, err := r.TenantID(ctx)
if err != nil {
return nil, err
}
if id == "" {
return nil, nil
}

return []string{id}, nil
}