feat service: introduce labels for logs/recorder (#104)
This commit is contained in:
@@ -92,4 +92,8 @@ const (
|
||||
|
||||
// MDKeyDialTimeout sets the dial timeout for outbound connections.
|
||||
MDKeyDialTimeout = "dialTimeout"
|
||||
|
||||
// MDKeyLabels holds static key/value labels attached to a service's
|
||||
// records and logs.
|
||||
MDKeyLabels = "labels"
|
||||
)
|
||||
|
||||
@@ -115,6 +115,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
var observerPeriod time.Duration
|
||||
var netnsIn, netnsOut string
|
||||
var dialTimeout time.Duration
|
||||
var labels map[string]string
|
||||
|
||||
var limiterRefreshInterval time.Duration
|
||||
var limiterCleanupInterval time.Duration
|
||||
@@ -150,6 +151,14 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
limiterRefreshInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterRefreshInterval)
|
||||
limiterCleanupInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterCleanupInterval)
|
||||
limiterScope = mdutil.GetString(md, parsing.MDKeyLimiterScope)
|
||||
|
||||
labels = mdutil.GetStringMapString(md, parsing.MDKeyLabels)
|
||||
}
|
||||
|
||||
if len(labels) > 0 {
|
||||
serviceLogger = serviceLogger.WithFields(map[string]any{
|
||||
"labels": labels,
|
||||
})
|
||||
}
|
||||
|
||||
listenerLogger := serviceLogger.WithFields(map[string]any{
|
||||
@@ -349,6 +358,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)),
|
||||
xservice.ObserverPeriodOption(observerPeriod),
|
||||
xservice.LoggerOption(serviceLogger),
|
||||
xservice.LabelsOption(labels),
|
||||
)
|
||||
|
||||
serviceLogger.Infof("listening on %s/%s", s.Addr().String(), s.Addr().Network())
|
||||
|
||||
@@ -100,3 +100,17 @@ func ClientIDFromContext(ctx context.Context) ClientID {
|
||||
v, _ := ctx.Value(clientIDKey{}).(ClientID)
|
||||
return v
|
||||
}
|
||||
|
||||
// labelsKey saves the static service labels.
|
||||
type labelsKey struct{}
|
||||
|
||||
// ContextWithLabels returns a new context carrying the given service labels.
|
||||
func ContextWithLabels(ctx context.Context, labels map[string]string) context.Context {
|
||||
return context.WithValue(ctx, labelsKey{}, labels)
|
||||
}
|
||||
|
||||
// LabelsFromContext returns the service labels stored in the context, or nil.
|
||||
func LabelsFromContext(ctx context.Context) map[string]string {
|
||||
v, _ := ctx.Value(labelsKey{}).(map[string]string)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package ctx
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -149,6 +150,29 @@ func TestClientIDFromContext_WrongType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextWithLabels(t *testing.T) {
|
||||
labels := map[string]string{"tenant": "acme", "region": "eu"}
|
||||
ctx := ContextWithLabels(context.Background(), labels)
|
||||
|
||||
got := LabelsFromContext(ctx)
|
||||
if !reflect.DeepEqual(got, labels) {
|
||||
t.Errorf("LabelsFromContext() = %v, want %v", got, labels)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelsFromContext_Empty(t *testing.T) {
|
||||
if got := LabelsFromContext(context.Background()); got != nil {
|
||||
t.Errorf("LabelsFromContext(empty) = %v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelsFromContext_WrongType(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), labelsKey{}, "not-a-map")
|
||||
if got := LabelsFromContext(ctx); got != nil {
|
||||
t.Errorf("LabelsFromContext(wrong type) = %v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleValuesInContext(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx = ContextWithSrcAddr(ctx, &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 8080})
|
||||
|
||||
+16
-10
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/recorder"
|
||||
xctx "github.com/go-gost/x/ctx"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -82,16 +83,17 @@ type DNSRecorderObject struct {
|
||||
// level — addresses, protocols, transferred bytes, and optional sub-records
|
||||
// for HTTP, WebSocket, TLS, and DNS traffic.
|
||||
type HandlerRecorderObject struct {
|
||||
Node string `json:"node,omitempty"`
|
||||
Service string `json:"service"`
|
||||
Network string `json:"network"`
|
||||
RemoteAddr string `json:"remote"`
|
||||
LocalAddr string `json:"local"`
|
||||
ClientAddr string `json:"client"`
|
||||
SrcAddr string `json:"src"`
|
||||
DstAddr string `json:"dst"`
|
||||
Host string `json:"host"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
Node string `json:"node,omitempty"`
|
||||
Service string `json:"service"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Network string `json:"network"`
|
||||
RemoteAddr string `json:"remote"`
|
||||
LocalAddr string `json:"local"`
|
||||
ClientAddr string `json:"client"`
|
||||
SrcAddr string `json:"src"`
|
||||
DstAddr string `json:"dst"`
|
||||
Host string `json:"host"`
|
||||
Proto string `json:"proto,omitempty"`
|
||||
ClientIP string `json:"clientIP"`
|
||||
ClientID string `json:"clientID,omitempty"`
|
||||
HTTP *HTTPRecorderObject `json:"http,omitempty"`
|
||||
@@ -115,6 +117,10 @@ func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder)
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.Labels == nil {
|
||||
p.Labels = xctx.LabelsFromContext(ctx)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -43,6 +43,7 @@ type options struct {
|
||||
observer observer.Observer
|
||||
observerPeriod time.Duration
|
||||
logger logger.Logger
|
||||
labels map[string]string
|
||||
}
|
||||
|
||||
// Option is a functional option for configuring a service.
|
||||
@@ -113,6 +114,14 @@ func ObserverPeriodOption(period time.Duration) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// LabelsOption sets the static labels attached to the service's records
|
||||
// and logs.
|
||||
func LabelsOption(labels map[string]string) Option {
|
||||
return func(opts *options) {
|
||||
opts.labels = labels
|
||||
}
|
||||
}
|
||||
|
||||
// LoggerOption sets the logger for the service. If not provided, a no-op
|
||||
// logger is used.
|
||||
func LoggerOption(logger logger.Logger) Option {
|
||||
@@ -245,6 +254,10 @@ func (s *defaultService) Serve() error {
|
||||
sid := xid.New().String()
|
||||
ctx = xctx.ContextWithSid(ctx, xctx.Sid(sid))
|
||||
|
||||
if len(s.options.labels) > 0 {
|
||||
ctx = xctx.ContextWithLabels(ctx, s.options.labels)
|
||||
}
|
||||
|
||||
log := s.options.logger.WithFields(map[string]any{
|
||||
"sid": sid,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user