-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes 3
- Loading branch information
Showing
16 changed files
with
670 additions
and
27 deletions.
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
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 |
---|---|---|
@@ -1,20 +1,67 @@ | ||
package clog | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log/slog" | ||
|
||
"github.com/ronny/clog/trace" | ||
) | ||
|
||
type HandlerOptions = slog.HandlerOptions | ||
var ErrInvalidHandlerOptions = errors.New("invalid HandlerOptions") | ||
|
||
// NewHandler returns a [log/slog.JSONHandler] pre-configured for Google Cloud | ||
// Logging. | ||
// | ||
// Basically it replaces `opts.ReplaceAttr` with [ReplaceAttr]. | ||
func NewHandler(w io.Writer, opts *HandlerOptions) *slog.JSONHandler { | ||
if opts == nil { | ||
opts = &HandlerOptions{} | ||
} | ||
type HandlerOptions struct { | ||
AddSource bool | ||
Level slog.Leveler | ||
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr | ||
GoogleProjectID string | ||
} | ||
|
||
var _ slog.Handler = (*Handler)(nil) | ||
|
||
// Handler is a [log/slog.JSONHandler] preconfigured for Google Cloud Logging. | ||
type Handler struct { | ||
opts HandlerOptions | ||
handler slog.Handler | ||
} | ||
|
||
func NewHandler(w io.Writer, opts HandlerOptions) (*Handler, error) { | ||
opts.ReplaceAttr = ReplaceAttr | ||
return slog.NewJSONHandler(w, opts) | ||
if opts.GoogleProjectID == "" { | ||
return nil, fmt.Errorf("%w: missing GoogleProjectID", ErrInvalidHandlerOptions) | ||
} | ||
return &Handler{ | ||
opts: opts, | ||
handler: slog.NewJSONHandler(w, &slog.HandlerOptions{ | ||
AddSource: opts.AddSource, | ||
Level: opts.Level, | ||
ReplaceAttr: opts.ReplaceAttr, | ||
}), | ||
}, nil | ||
} | ||
|
||
// Handle implements [log/slog.Handler]. | ||
func (h *Handler) Handle(ctx context.Context, record slog.Record) error { | ||
return h.handler.Handle(ctx, | ||
trace.NewRecord(ctx, record, h.opts.GoogleProjectID), | ||
) | ||
} | ||
|
||
// Enabled implements [log/slog.Handler]. | ||
func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool { | ||
return h.handler.Enabled(ctx, level) | ||
} | ||
|
||
// WithAttrs implements [log/slog.Handler]. | ||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
h.handler = h.handler.WithAttrs(attrs) | ||
return h | ||
} | ||
|
||
// WithGroup implements [log/slog.Handler]. | ||
func (h *Handler) WithGroup(name string) slog.Handler { | ||
h.handler = h.handler.WithGroup(name) | ||
return h | ||
} |
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,87 @@ | ||
package trace | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
) | ||
|
||
var _ Trace = (*CloudTraceContext)(nil) | ||
|
||
// TRACE_ID/SPAN_ID();o=OPTIONS | ||
// Example: 70e0091f6f5d4643bb4eca9d81320c76/97123319527522;o=1 | ||
var cloudTraceContextRegex = regexp.MustCompile(`^(?P<TraceID>[0-9a-fA-F]{32})/(?P<SpanID>[0-9]+);o=(?P<Options>.+)$`) | ||
|
||
// Deprecated: use [ParseW3CTraceParent] instead. | ||
func ParseCloudTraceContext(s string) (*CloudTraceContext, error) { | ||
match := cloudTraceContextRegex.FindStringSubmatch(s) | ||
result := make(map[string]string) | ||
for i, name := range cloudTraceContextRegex.SubexpNames() { | ||
if i != 0 && name != "" { | ||
result[name] = match[i] | ||
} | ||
} | ||
t := &CloudTraceContext{ | ||
TraceID: result["TraceID"], | ||
SpanID: result["SpanID"], | ||
Options: result["Options"], | ||
} | ||
err := t.Validate() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return t, nil | ||
} | ||
|
||
// Based on https://cloud.google.com/trace/docs/trace-context#http-requests. | ||
// See also https://cloud.google.com/run/docs/trace for Cloud Run specific info. | ||
// | ||
// Deprecated: use [W3CTraceParent] instead. | ||
type CloudTraceContext struct { | ||
TraceID string | ||
SpanID string | ||
Options string | ||
} | ||
|
||
func (t *CloudTraceContext) Validate() error { | ||
if t == nil { | ||
return fmt.Errorf("%w: nil CloudTraceContext", ErrInvalidTrace) | ||
} | ||
if len(t.TraceID) != 32 { | ||
return fmt.Errorf("%w: invalid TraceID", ErrInvalidTrace) | ||
} | ||
if t.SpanID == "" { | ||
return fmt.Errorf("%w: invalid SpanID", ErrInvalidTrace) | ||
} | ||
if t.Options == "" { | ||
return fmt.Errorf("%w: invalid Options", ErrInvalidTrace) | ||
} | ||
return nil | ||
} | ||
|
||
func (t *CloudTraceContext) GetTraceID() string { | ||
if t == nil { | ||
return "" | ||
} | ||
return t.TraceID | ||
} | ||
|
||
func (t *CloudTraceContext) GetSpanID() string { | ||
if t == nil { | ||
return "" | ||
} | ||
return t.SpanID | ||
} | ||
|
||
func (t *CloudTraceContext) GetOptions() string { | ||
if t == nil { | ||
return "" | ||
} | ||
return t.Options | ||
} | ||
|
||
func (t *CloudTraceContext) Sampled() bool { | ||
if t == nil { | ||
return false | ||
} | ||
return t.Options == "1" | ||
} |
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,21 @@ | ||
package trace_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/ronny/clog/trace" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestCloudTraceContext(t *testing.T) { | ||
validExample := "70e0091f6f5d4643bb4eca9d81320c76/97123319527522;o=1" | ||
|
||
tr, err := trace.ParseCloudTraceContext(validExample) | ||
if !assert.Nil(t, err) { | ||
return | ||
} | ||
|
||
assert.Equal(t, "70e0091f6f5d4643bb4eca9d81320c76", tr.GetTraceID()) | ||
assert.Equal(t, "97123319527522", tr.GetSpanID()) | ||
assert.Equal(t, true, tr.Sampled()) | ||
} |
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,22 @@ | ||
package trace | ||
|
||
import "context" | ||
|
||
type ctxKeyType int | ||
|
||
const ctxKey ctxKeyType = iota | ||
|
||
// NewContext returns a new context derived from ctx with trace attached. | ||
func NewContext(ctx context.Context, t Trace) context.Context { | ||
return context.WithValue(ctx, ctxKey, t) | ||
} | ||
|
||
// FromContext extracts any [Trace] information from ctx and | ||
// returns it if found, otherwise returns nil. | ||
func FromContext(ctx context.Context) Trace { | ||
t, ok := ctx.Value(ctxKey).(Trace) | ||
if !ok { | ||
return nil | ||
} | ||
return t | ||
} |
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,3 @@ | ||
// Package trace contains utilities to extract and use Cloud Trace context in | ||
// logs. | ||
package trace |
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,8 @@ | ||
package trace | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrUnparseable = errors.New("unparseable") | ||
ErrInvalidTrace = errors.New("invalid trace") | ||
) |
Oops, something went wrong.