-
Notifications
You must be signed in to change notification settings - Fork 69
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
Feature: Add dslog and tenant packages #33
Closed
Changes from 9 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
cc94164
Move some utility functions out of `util` and into their own packages…
kminehart 4795265
Remove util.Logger and move util.InitLogger to util/log package. (#3781)
pstibrany 7a30a89
Chore: Upgrade weaveworks/common (#4462)
aknuds1 3120328
Add tenant resolver (#3486)
simonswine 615a344
Add multi tenant query federation (#3250)
simonswine d1ef33a
Restrict path segments in TenantIDs (#4375) (#4376)
bboreham dfe7611
Move log and tenant packages to root
aknuds1 b71e41c
Remove globals
aknuds1 c351ce3
Update changelog
aknuds1 a4de400
Fix changelog
aknuds1 30d0c6a
Merge remote-tracking branch 'origin/main' into feat/log
aknuds1 893e0f0
Reduce tenant API
aknuds1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package dslog | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/go-kit/kit/log/level" | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promauto" | ||
"github.com/weaveworks/common/logging" | ||
) | ||
|
||
// PrometheusLogger exposes Prometheus counters for each of go-kit's log levels. | ||
type PrometheusLogger struct { | ||
logger log.Logger | ||
logMessages *prometheus.CounterVec | ||
experimentalFeaturesInUse prometheus.Counter | ||
} | ||
|
||
// NewPrometheusLogger creates a new instance of PrometheusLogger which exposes | ||
// Prometheus counters for various log levels. | ||
func NewPrometheusLogger(l logging.Level, format logging.Format, reg prometheus.Registerer, metricsNamespace string) log.Logger { | ||
logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) | ||
if format.String() == "json" { | ||
logger = log.NewJSONLogger(log.NewSyncWriter(os.Stderr)) | ||
} | ||
logger = level.NewFilter(logger, l.Gokit) | ||
|
||
logMessages := promauto.With(reg).NewCounterVec(prometheus.CounterOpts{ | ||
Name: "log_messages_total", | ||
Help: "Total number of log messages.", | ||
}, []string{"level"}) | ||
// Initialise counters for all supported levels: | ||
supportedLevels := []level.Value{ | ||
level.DebugValue(), | ||
level.InfoValue(), | ||
level.WarnValue(), | ||
level.ErrorValue(), | ||
} | ||
for _, level := range supportedLevels { | ||
logMessages.WithLabelValues(level.String()) | ||
} | ||
|
||
logger = &PrometheusLogger{ | ||
logger: logger, | ||
logMessages: logMessages, | ||
experimentalFeaturesInUse: promauto.With(reg).NewCounter( | ||
prometheus.CounterOpts{ | ||
Namespace: metricsNamespace, | ||
Name: "experimental_features_in_use_total", | ||
Help: "The number of experimental features in use.", | ||
}, | ||
), | ||
} | ||
|
||
// return a Logger without caller information, shouldn't use directly | ||
logger = log.With(logger, "ts", log.DefaultTimestampUTC) | ||
return logger | ||
} | ||
|
||
// Log increments the appropriate Prometheus counter depending on the log level. | ||
func (pl *PrometheusLogger) Log(kv ...interface{}) error { | ||
pl.logger.Log(kv...) | ||
l := "unknown" | ||
for i := 1; i < len(kv); i += 2 { | ||
if v, ok := kv[i].(level.Value); ok { | ||
l = v.String() | ||
break | ||
} | ||
} | ||
pl.logMessages.WithLabelValues(l).Inc() | ||
return nil | ||
} | ||
|
||
// CheckFatal prints an error and exits with error code 1 if err is non-nil. | ||
func CheckFatal(location string, err error, logger log.Logger) { | ||
if err != nil { | ||
logger := level.Error(logger) | ||
if location != "" { | ||
logger = log.With(logger, "msg", "error "+location) | ||
} | ||
// %+v gets the stack trace from errors using github.com/pkg/errors | ||
logger.Log("err", fmt.Sprintf("%+v", err)) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// WarnExperimentalUse logs a warning and increments the experimental features metric. | ||
func WarnExperimentalUse(feature string, logger log.Logger) { | ||
level.Warn(logger).Log("msg", "experimental feature in use", "feature", feature) | ||
if pl, ok := logger.(*PrometheusLogger); ok { | ||
pl.experimentalFeaturesInUse.Inc() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package dslog | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/weaveworks/common/tracing" | ||
|
||
"github.com/grafana/dskit/tenant" | ||
) | ||
|
||
// WithUserID returns a Logger that has information about the current user in | ||
// its details. | ||
func WithUserID(userID string, l log.Logger) log.Logger { | ||
// See note in WithContext. | ||
return log.With(l, "org_id", userID) | ||
} | ||
|
||
// WithTraceID returns a Logger that has information about the traceID in | ||
// its details. | ||
func WithTraceID(traceID string, l log.Logger) log.Logger { | ||
// See note in WithContext. | ||
return log.With(l, "traceID", traceID) | ||
} | ||
|
||
// WithContext returns a Logger that has information about the current user in | ||
// its details. | ||
// | ||
// e.g. | ||
// log := util.WithContext(ctx) | ||
// log.Errorf("Could not chunk chunks: %v", err) | ||
func WithContext(ctx context.Context, l log.Logger) 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, err := tenant.ID(ctx) | ||
if err == nil { | ||
l = WithUserID(userID, l) | ||
} | ||
|
||
traceID, ok := tracing.ExtractSampledTraceID(ctx) | ||
if !ok { | ||
return l | ||
} | ||
|
||
return WithTraceID(traceID, l) | ||
} | ||
|
||
// WithSourceIPs returns a Logger that has information about the source IPs in | ||
// its details. | ||
func WithSourceIPs(sourceIPs string, l log.Logger) log.Logger { | ||
return log.With(l, "sourceIPs", sourceIPs) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package tenant | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/weaveworks/common/user" | ||
) | ||
|
||
var defaultResolver Resolver = NewSingleResolver() | ||
|
||
// WithDefaultResolver updates the resolver used for the package methods. | ||
func WithDefaultResolver(r Resolver) { | ||
defaultResolver = r | ||
} | ||
|
||
// ID returns exactly a single tenant ID from the context. It should be | ||
// used when a certain endpoint should only support exactly a single | ||
// tenant ID. It returns an error user.ErrNoOrgID if there is no tenant ID | ||
// supplied or user.ErrTooManyOrgIDs if there are multiple tenant IDs present. | ||
func ID(ctx context.Context) (string, error) { | ||
return defaultResolver.TenantID(ctx) | ||
} | ||
|
||
// IDs returns all tenant IDs from the context. It should return | ||
// normalized list of ordered and distinct tenant IDs (as produced by | ||
// NormalizeTenantIDs). | ||
func IDs(ctx context.Context) ([]string, error) { | ||
return defaultResolver.TenantIDs(ctx) | ||
} | ||
|
||
type Resolver interface { | ||
// TenantID returns exactly a single tenant ID from the context. It should be | ||
// used when a certain endpoint should only support exactly a single | ||
// tenant ID. It returns an error user.ErrNoOrgID if there is no tenant ID | ||
// supplied or user.ErrTooManyOrgIDs if there are multiple tenant IDs present. | ||
TenantID(context.Context) (string, error) | ||
|
||
// TenantIDs returns all tenant IDs from the context. It should return | ||
// normalized list of ordered and distinct tenant IDs (as produced by | ||
// NormalizeTenantIDs). | ||
TenantIDs(context.Context) ([]string, error) | ||
} | ||
|
||
// NewSingleResolver creates a tenant resolver, which restricts all requests to | ||
// be using a single tenant only. This allows a wider set of characters to be | ||
// used within the tenant ID and should not impose a breaking change. | ||
func NewSingleResolver() *SingleResolver { | ||
return &SingleResolver{} | ||
} | ||
|
||
type SingleResolver struct { | ||
} | ||
|
||
// containsUnsafePathSegments will return true if the string is a directory | ||
// reference like `.` and `..` or if any path separator character like `/` and | ||
// `\` can be found. | ||
func containsUnsafePathSegments(id string) bool { | ||
// handle the relative reference to current and parent path. | ||
if id == "." || id == ".." { | ||
return true | ||
} | ||
|
||
return strings.ContainsAny(id, "\\/") | ||
} | ||
|
||
var errInvalidTenantID = errors.New("invalid tenant ID") | ||
|
||
func (t *SingleResolver) TenantID(ctx context.Context) (string, error) { | ||
//lint:ignore faillint wrapper around upstream method | ||
id, err := user.ExtractOrgID(ctx) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if containsUnsafePathSegments(id) { | ||
return "", errInvalidTenantID | ||
} | ||
|
||
return id, nil | ||
} | ||
|
||
func (t *SingleResolver) TenantIDs(ctx context.Context) ([]string, error) { | ||
orgID, err := t.TenantID(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return []string{orgID}, err | ||
} | ||
|
||
type MultiResolver struct { | ||
} | ||
|
||
// NewMultiResolver creates a tenant resolver, which allows request to have | ||
// multiple tenant ids submitted separated by a '|' character. This enforces | ||
// further limits on the character set allowed within tenants as detailed here: | ||
// https://cortexmetrics.io/docs/guides/limitations/#tenant-id-naming) | ||
func NewMultiResolver() *MultiResolver { | ||
return &MultiResolver{} | ||
} | ||
|
||
func (t *MultiResolver) TenantID(ctx context.Context) (string, error) { | ||
orgIDs, err := t.TenantIDs(ctx) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if len(orgIDs) > 1 { | ||
return "", user.ErrTooManyOrgIDs | ||
} | ||
|
||
return orgIDs[0], nil | ||
} | ||
|
||
func (t *MultiResolver) TenantIDs(ctx context.Context) ([]string, error) { | ||
//lint:ignore faillint wrapper around upstream method | ||
orgID, err := user.ExtractOrgID(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
orgIDs := strings.Split(orgID, tenantIDsLabelSeparator) | ||
for _, orgID := range orgIDs { | ||
if err := ValidTenantID(orgID); err != nil { | ||
return nil, err | ||
} | ||
if containsUnsafePathSegments(orgID) { | ||
return nil, errInvalidTenantID | ||
} | ||
} | ||
|
||
return NormalizeTenantIDs(orgIDs), nil | ||
} | ||
|
||
// ExtractTenantIDFromHTTPRequest extracts a single TenantID through a given | ||
// resolver directly from a HTTP request. | ||
func ExtractTenantIDFromHTTPRequest(req *http.Request) (string, context.Context, error) { | ||
//lint:ignore faillint wrapper around upstream method | ||
_, ctx, err := user.ExtractOrgIDFromHTTPRequest(req) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
|
||
tenantID, err := defaultResolver.TenantID(ctx) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
|
||
return tenantID, ctx, nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of like the look of this, do you think we should do this on all of these changes going forward? We could also add a new tag like [Addition] or something if we'd like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@treid314 I'm actually not 100% sure, I just thought it was the right thing to do since @pracucci asked us to keep the changelog up to date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've mostly been using the changelog to track any constructor or method input changes, there just haven't really been any for a bit. I think it's great to track adding modules as well to show us whats in here.