diff --git a/Makefile b/Makefile index 95fba46..a0a9ba3 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CWD = $(shell pwd) define HELP_TEXT -Welcome to obkit! +Welcome to okit! Targets: help provides help text diff --git a/README.md b/README.md index 2b4f410..039a5a0 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,8 @@ func main() { okit.Emit("user_signup", tags...) // Tracing - ctx, done := okit.Trace(context.Background(), tags...) - defer done() - _ = ctx.Err() // not needed, removes unused error + ctx := context.Background() + defer okit.Trace(&ctx, tags...).Done() // Logging okit.Debug("a message used for debugging", tags...) diff --git a/api.go b/api.go index fd911f1..c7c0fed 100644 --- a/api.go +++ b/api.go @@ -22,9 +22,61 @@ package okit import ( "context" - "strings" + "fmt" ) +const ( + // LevelTrace is used to enable bookend events for tracing. + LevelTrace Level = iota + // LevelDebug enables debug level logging for the application. + LevelDebug + // LevelInfo is the default level of logging for applications. + LevelInfo + // LevelWarn decreases log verbosity by removing informational messages from the stream. + LevelWarn + // LevelError further decreases log verbosity by only surfacing errors encountered by the system. + LevelError +) + +// Level allows the logging level to be configured. +type Level uint8 + +// Set allows a given log level to be set to a certain value. +func (l *Level) Set(val string) error { + switch val { + case "trace", "TRACE": + *l = LevelTrace + case "debug", "DEBUG": + *l = LevelTrace + case "info", "INFO": + *l = LevelTrace + case "warn", "WARN": + *l = LevelTrace + case "error", "ERROR": + *l = LevelTrace + } + + return fmt.Errorf("unrecognized level") +} + +// String returns the string representation of the Level. +func (l *Level) String() string { + switch *l { + case LevelTrace: + return "trace" + case LevelDebug: + return "debug" + case LevelInfo: + return "info" + case LevelWarn: + return "warn" + case LevelError: + return "error" + } + + return "unknown" +} + // Logger defines common operations for writing log messages. type Logger interface { // Debug adds a debug message to the event stream, if configured. @@ -55,7 +107,7 @@ type DoneFunc func() // Tracer allows method calls to be instrumented for debugging. type Tracer interface { // Trace captures the method calling context and returns a function that can be invoked to complete the trace. - Trace(ctx context.Context, tags ...Tag) (context.Context, DoneFunc) + Trace(ctx *context.Context, tags ...Tag) ActiveTrace } // Span defines an internal structure for tracking traces across an application. This structure is only used for @@ -83,48 +135,8 @@ type Interface[T any] interface { Wither[T] } -type Level int8 - -const ( - UnknownLevel = 0 - TraceLevel = 1 - DebugLevel = 2 - InfoLevel = 3 - WarnLevel = 4 - ErrorLevel = 5 -) - -func (l *Level) String() string { - switch *l { - case TraceLevel: - return "trace" - case DebugLevel: - return "debug" - case InfoLevel: - return "info" - case WarnLevel: - return "warn" - case ErrorLevel: - return "error" - } - - return "unknown" -} - -func (l *Level) Set(val string) error { - switch { - case strings.EqualFold("trace", val): - *l = TraceLevel - case strings.EqualFold("debug", val): - *l = DebugLevel - case strings.EqualFold("info", val): - *l = InfoLevel - case strings.EqualFold("warn", val): - *l = WarnLevel - case strings.EqualFold("error", val): - *l = ErrorLevel - } - - *l = UnknownLevel - return nil +// ActiveTrace defines an abstraction that allows traces to be completed using a Done function. +type ActiveTrace interface { + // Done finishes the current tracing operation. + Done() } diff --git a/client.go b/client.go index 8d22229..5695056 100644 --- a/client.go +++ b/client.go @@ -26,88 +26,103 @@ import ( "encoding/base32" "io" "runtime" + "sync" "time" "go.pitz.tech/okit/pb" ) -var encoding = base32.StdEncoding.WithPadding(base32.NoPadding) +var ( + encoding = base32.StdEncoding.WithPadding(base32.NoPadding) + + defaultUUID = func() string { + buf := make([]byte, 16) + n, err := io.ReadFull(rand.Reader, buf) + if err != nil { + panic(err) + } + + return encoding.EncodeToString(buf[:n]) + } +) // NewClient produces a new default client implementation for emitting metrics. func NewClient() Client { return Client{ - now: time.Now, - uuid: func() string { - buf := make([]byte, 16) - n, err := io.ReadFull(rand.Reader, buf) - if err != nil { - panic(err) - } - - return encoding.EncodeToString(buf[:n]) - }, + level: LevelInfo, + now: time.Now, + uuid: defaultUUID, callerSkip: 2, } } // Client provides a default implementation. type Client struct { + level Level now func() time.Time uuid func() string tags []Tag callerSkip int } +// WithLevel allows the current logging level to be tuned. This does not prevent traces from being emit, or metrics +// from being reported. It only modifies the behavior of logs. +func (c Client) WithLevel(level Level) Client { + c.level = level + return c +} + // WithNow configures the function that's used to obtain the current timestamp. -func (o Client) WithNow(now func() time.Time) Client { - o.now = now - return o +func (c Client) WithNow(now func() time.Time) Client { + c.now = now + return c } // WithUUID returns a new uuid that uniquely identifies traces, spans, and tags. -func (o Client) WithUUID(uuid func() string) Client { - o.uuid = uuid - return o +func (c Client) WithUUID(uuid func() string) Client { + c.uuid = uuid + return c } // WithCallerSkip is used to configure the number of frames to skip when determining the caller. Callers are // predominantly used when performing traces. -func (o Client) WithCallerSkip(callerSkip int) Client { - o.callerSkip = callerSkip - return o +func (c Client) WithCallerSkip(callerSkip int) Client { + c.callerSkip = callerSkip + return c } // With appends tags to the current client that will automatically be added to all events, metrics, logs, and traces. -func (o Client) With(tags ...Tag) Client { - o.tags = append(o.tags, tags...) - return o +func (c Client) With(tags ...Tag) Client { + c.tags = append(c.tags, tags...) + return c } // Emit allows for the emission of an event which has now value and only associated tags. -func (o Client) Emit(event string, tags ...Tag) { - o.With(tags...).emit(&pb.Entry{ +func (c Client) Emit(event string, tags ...Tag) { + c.With(tags...).emit(&pb.Entry{ Kind: pb.Kind_Event, Scope: event, }) } // Observe reports a metric and it's associated value. -func (o Client) Observe(metric string, value float64, tags ...Tag) { - o.With(tags...).emit(&pb.Entry{ - Value: &pb.Entry_Double{Double: value}, +func (c Client) Observe(metric string, value float64, tags ...Tag) { + c.With(tags...).With(Float64("value", value)).emit(&pb.Entry{ Kind: pb.Kind_Metric, Scope: metric, }) } // Trace allows a method to be traced, and it's execution time recorded and reported for viewing. -func (o Client) Trace(ctx context.Context, tags ...Tag) (context.Context, DoneFunc) { - name, _ := caller(o.callerSkip) - start := o.now() +func (c Client) Trace(ctxp *context.Context, tags ...Tag) ActiveTrace { + ctx := *ctxp + + name, _ := caller(c.callerSkip) + start := c.now() span := Span{ - TraceID: o.uuid(), - ID: o.uuid(), + TraceID: c.uuid(), + ID: c.uuid(), } v := ctx.Value(SpanKey) @@ -120,86 +135,120 @@ func (o Client) Trace(ctx context.Context, tags ...Tag) (context.Context, DoneFu } //goland:noinspection GoAssignmentToReceiver - o = o.With( - append( - []Tag{ - String("traceId", span.TraceID), - String("traceSpanId", span.ID), - String("traceParentId", span.ParentID), - }, - tags..., - )..., - ) + c = c. + With( + String("traceId", span.TraceID), + String("traceSpanId", span.ID), + String("traceParentId", span.ParentID), + ). + With(tags...) - // bookend - o.With(String("bookend", "start")).emit(&pb.Entry{ - Kind: pb.Kind_Log, - Scope: name, - Value: &pb.Entry_String_{String_: "trace"}, - }) + if c.level <= LevelTrace { + c.With(String("bookend", "start"), String("fn", name)). + emit(&pb.Entry{ + Kind: pb.Kind_Log, + Scope: "trace", + }) + } - return context.WithValue(ctx, SpanKey, span), func() { - duration := o.now().Sub(start) + ctx = context.WithValue(ctx, SpanKey, span) + *ctxp = ctx - // bookend - o.With(String("bookend", "end")).emit(&pb.Entry{ - Kind: pb.Kind_Log, - Scope: name, - Value: &pb.Entry_String_{String_: "trace"}, - }) + return &active{name, start, c, sync.Once{}} +} + +type active struct { + name string + start time.Time + c Client + once sync.Once +} + +func (a *active) Done() { + a.once.Do(func() { + duration := a.c.now().Sub(a.start) + + //goland:noinspection GoAssignmentToReceiver + a.c = a.c.With(Duration("duration", duration)) + + if a.c.level <= LevelTrace { + a.c.With(String("bookend", "end"), String("fn", a.name)).emit( + &pb.Entry{ + Kind: pb.Kind_Log, + Scope: "trace", + }, + ) + } // trace - o.emit( - &pb.Entry{ - Kind: pb.Kind_Trace, - Scope: name, - Value: &pb.Entry_Duration{Duration: pb.DurationPB(duration)}, - }, - ) - } + a.c.emit(&pb.Entry{ + Kind: pb.Kind_Trace, + Scope: a.name, + }) + }) } // Debug produces a debug log event. -func (o Client) Debug(msg string, tags ...Tag) { - o.With(tags...).emit(&pb.Entry{ - Kind: pb.Kind_Log, - Scope: msg, - Value: &pb.Entry_String_{String_: "debug"}, - }) +func (c Client) Debug(msg string, tags ...Tag) { + if c.level > LevelDebug { + return + } + + c.With(tags...). + With(String("msg", msg)). + emit(&pb.Entry{ + Kind: pb.Kind_Log, + Scope: "debug", + }) } // Info produces an information log event. -func (o Client) Info(msg string, tags ...Tag) { - o.With(tags...).emit(&pb.Entry{ - Kind: pb.Kind_Log, - Scope: msg, - Value: &pb.Entry_String_{String_: "info"}, - }) +func (c Client) Info(msg string, tags ...Tag) { + if c.level > LevelInfo { + return + } + + c.With(tags...). + With(String("msg", msg)). + emit(&pb.Entry{ + Kind: pb.Kind_Log, + Scope: "info", + }) } // Warn produces a warning that is surfaced to operators. -func (o Client) Warn(msg string, tags ...Tag) { - o.With(tags...).emit(&pb.Entry{ - Kind: pb.Kind_Log, - Scope: msg, - Value: &pb.Entry_String_{String_: "warn"}, - }) +func (c Client) Warn(msg string, tags ...Tag) { + if c.level > LevelWarn { + return + } + + c.With(tags...). + With(String("msg", msg)). + emit(&pb.Entry{ + Kind: pb.Kind_Log, + Scope: "warn", + }) } // Error produces a message that communicates an error has occurred. -func (o Client) Error(msg string, tags ...Tag) { - o.With(tags...).emit(&pb.Entry{ - Kind: pb.Kind_Log, - Scope: msg, - Value: &pb.Entry_String_{String_: "error"}, - }) +func (c Client) Error(msg string, tags ...Tag) { + if c.level > LevelError { + return + } + + c.With(tags...). + With(String("msg", msg)). + emit(&pb.Entry{ + Kind: pb.Kind_Log, + Scope: "error", + }) } -func (o Client) emit(entries ...*pb.Entry) { - now := pb.TimestampPB(o.now()) +func (c Client) emit(entries ...*pb.Entry) { + now := pb.TimestampPB(c.now()) - tags := make([]*pb.Tag, 0, len(o.tags)) - for _, tag := range o.tags { + tags := make([]*pb.Tag, 0, len(c.tags)) + for _, tag := range c.tags { tags = append(tags, tag.AsTagPB()) } diff --git a/examples/overview/main.go b/examples/overview/main.go index 3c1111a..93bb4f2 100644 --- a/examples/overview/main.go +++ b/examples/overview/main.go @@ -27,6 +27,8 @@ import ( ) func main() { + okit.DefaultClient = okit.DefaultClient.WithLevel(okit.LevelTrace) + var tags []okit.Tag // Metric emission @@ -39,9 +41,8 @@ func main() { okit.Emit("user_signup", tags...) // Tracing - ctx, done := okit.Trace(context.Background(), tags...) - defer done() - _ = ctx.Err() // not needed, removes unused error + ctx := context.Background() + defer okit.Trace(&ctx, tags...).Done() // Logging okit.Debug("a message used for debugging", tags...) diff --git a/format.go b/format.go index 97bb934..3553e98 100644 --- a/format.go +++ b/format.go @@ -1,8 +1,27 @@ +// Copyright (C) 2022 The OKit Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +// OR OTHER DEALINGS IN THE SOFTWARE. + package okit import ( "bufio" - "strconv" "strings" "time" @@ -18,6 +37,7 @@ type Format interface { // JSONFormat writes entries using JSON encoded protocol buffers. type JSONFormat struct { + // Marshaler Marshaler jsonpb.Marshaler } @@ -34,16 +54,6 @@ func (f TextFormat) Marshal(writer *bufio.Writer, entry *pb.Entry) error { sink.WriteByte('\t') sink.WriteString(strings.ToUpper(entry.Kind.String())) sink.WriteByte('\t') - - switch v := entry.GetValue().(type) { - case *pb.Entry_String_: - sink.WriteString(v.String_) - case *pb.Entry_Double: - sink.WriteString(strconv.FormatFloat(v.Double, 'f', 5, 64)) - case *pb.Entry_Duration: - sink.WriteString(v.Duration.AsDuration().String()) - } - sink.WriteByte('\t') sink.WriteString(entry.Scope) sink.WriteByte('\t') diff --git a/http/endpoint.go b/http/endpoint.go index 22edc53..5b8db9b 100644 --- a/http/endpoint.go +++ b/http/endpoint.go @@ -38,7 +38,7 @@ func (e *Endpoint) Health(w http.ResponseWriter, r *http.Request) { func (e *Endpoint) Metrics(w http.ResponseWriter, r *http.Request) { // Render metrics endpoint - // Unlike prometheus metrics are pushed, not scraped. So there's no need for complicated protocol here. + // Unlike prometheus, metrics are pushed and not scraped. So there's no need for a complicated protocol here. } func (e *Endpoint) Trace(w http.ResponseWriter, r *http.Request) { diff --git a/http/instrumentation.go b/http/instrumentation.go index 4a52904..efa7bb1 100644 --- a/http/instrumentation.go +++ b/http/instrumentation.go @@ -21,11 +21,19 @@ package http import ( + "context" "net/http" "go.pitz.tech/okit" ) +const ( + // TraceIDHeader is a string constant that defines a common key used to propagate an okit trace. + TraceIDHeader = "x-okit-trace-id" + // SpanIDHeader is a string constant that defines a common key used to propagate the parent span. + SpanIDHeader = "x-okit-span-id" +) + // InstrumentClient updates the provided http.Client to use an instrumented http.RoundTripper. If the provided // client.Transport is nil, then http.DefaultTransport is used. func InstrumentClient(client *http.Client) { @@ -46,16 +54,24 @@ type roundTripper struct { } func (r *roundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { + ctx := req.Context() statusCode := 0 - - ctx, done := okit.Trace(req.Context(), + tags := []okit.Tag{ okit.String("host", req.URL.Host), okit.String("method", req.Method), okit.String("path", req.URL.Path), okit.Intp("status", &statusCode), - okit.Errp(&err), - ) - defer done() + } + + defer okit.Trace(&ctx, tags...).Done() + + // propagate to server + if v := ctx.Value(okit.SpanKey); v != nil { + if span, ok := v.(okit.Span); ok { + req.Header.Set(TraceIDHeader, span.TraceID) + req.Header.Set(SpanIDHeader, span.ID) + } + } resp, err = r.delegate.RoundTrip(req.WithContext(ctx)) if err != nil { @@ -79,15 +95,27 @@ type handler struct { } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - statusCode := 200 + ctx := r.Context() - ctx, done := okit.Trace(r.Context(), + // propagation from client + traceID := r.Header.Get(TraceIDHeader) + spanID := r.Header.Get(SpanIDHeader) + if traceID != "" && spanID != "" { + ctx = context.WithValue(ctx, okit.SpanKey, okit.Span{ + TraceID: traceID, + ID: spanID, + }) + } + + statusCode := 200 + tags := []okit.Tag{ okit.String("host", r.URL.Host), okit.String("method", r.Method), okit.String("path", r.URL.Path), okit.Intp("status", &statusCode), - ) - defer done() + } + + defer okit.Trace(&ctx, tags...).Done() h.delegate.ServeHTTP( &response{ diff --git a/api_impl.go b/okit.go similarity index 89% rename from api_impl.go rename to okit.go index d88cdb8..8f66e69 100644 --- a/api_impl.go +++ b/okit.go @@ -57,22 +57,26 @@ func Observe(metric string, value float64, tags ...Tag) { } // Trace traces a function call. It implements distributed tracing and bookend events. -func Trace(ctx context.Context, tags ...Tag) (context.Context, DoneFunc) { - return DefaultClient.WithCallerSkip(3).Trace(ctx, tags...) +func Trace(ctxp *context.Context, tags ...Tag) ActiveTrace { + return DefaultClient.WithCallerSkip(3).Trace(ctxp, tags...) } +// Debug writes a message at a debug level. func Debug(msg string, tags ...Tag) { DefaultClient.WithCallerSkip(3).Debug(msg, tags...) } +// Info writes a message at a informational level. func Info(msg string, tags ...Tag) { DefaultClient.WithCallerSkip(3).Info(msg, tags...) } +// Warn writes a message at a warning level. func Warn(msg string, tags ...Tag) { DefaultClient.WithCallerSkip(3).Warn(msg, tags...) } +// Error writes a message at a error level. func Error(msg string, tags ...Tag) { DefaultClient.WithCallerSkip(3).Error(msg, tags...) } diff --git a/pb/obkit.pb.go b/pb/obkit.pb.go index c7b28a0..8064a89 100644 --- a/pb/obkit.pb.go +++ b/pb/obkit.pb.go @@ -385,13 +385,6 @@ type Entry struct { Kind Kind `protobuf:"varint,3,opt,name=kind,proto3,enum=obkit.Kind" json:"kind,omitempty"` // Tags is an optional field that contains a list of metadata associated with the entry. Tags []*Tag `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty"` - // Value is an optional field that defines a value associated with the entry (e.g. in the case of a log, metric, or trace). - // - // Types that are assignable to Value: - // *Entry_String_ - // *Entry_Double - // *Entry_Duration - Value isEntry_Value `protobuf_oneof:"value"` } func (x *Entry) Reset() { @@ -454,56 +447,6 @@ func (x *Entry) GetTags() []*Tag { return nil } -func (m *Entry) GetValue() isEntry_Value { - if m != nil { - return m.Value - } - return nil -} - -func (x *Entry) GetString_() string { - if x, ok := x.GetValue().(*Entry_String_); ok { - return x.String_ - } - return "" -} - -func (x *Entry) GetDouble() float64 { - if x, ok := x.GetValue().(*Entry_Double); ok { - return x.Double - } - return 0 -} - -func (x *Entry) GetDuration() *Duration { - if x, ok := x.GetValue().(*Entry_Duration); ok { - return x.Duration - } - return nil -} - -type isEntry_Value interface { - isEntry_Value() -} - -type Entry_String_ struct { - String_ string `protobuf:"bytes,5,opt,name=string,proto3,oneof"` // used for logs -} - -type Entry_Double struct { - Double float64 `protobuf:"fixed64,6,opt,name=double,proto3,oneof"` // used for metrics -} - -type Entry_Duration struct { - Duration *Duration `protobuf:"bytes,7,opt,name=duration,proto3,oneof"` // used for traces -} - -func (*Entry_String_) isEntry_Value() {} - -func (*Entry_Double) isEntry_Value() {} - -func (*Entry_Duration) isEntry_Value() {} - // ClientInfo is used to capture specific information about the client application that is emitting information. type ClientInfo struct { state protoimpl.MessageState @@ -647,7 +590,7 @@ var file_obkit_proto_rawDesc = []byte{ 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0xfa, 0x01, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x09, 0x74, + 0x65, 0x22, 0x8e, 0x01, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x73, @@ -656,29 +599,23 @@ var file_obkit_proto_rawDesc = []byte{ 0x0b, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x54, 0x61, 0x67, 0x52, 0x04, 0x74, 0x61, - 0x67, 0x73, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x06, - 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x06, - 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, - 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x08, 0x64, 0x75, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3c, - 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x5b, 0x0a, 0x06, - 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x12, 0x26, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x2a, 0x3e, 0x0a, 0x04, 0x4b, 0x69, 0x6e, - 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x09, - 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x72, 0x61, 0x63, 0x65, 0x10, 0x03, - 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x10, 0x04, 0x42, 0x1c, 0x5a, 0x1a, 0x63, 0x6f, 0x64, - 0x65, 0x2e, 0x70, 0x69, 0x74, 0x7a, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x6d, 0x79, 0x61, 0x2f, - 0x6f, 0x6b, 0x69, 0x74, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x73, 0x22, 0x3c, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x22, 0x5b, 0x0a, 0x06, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x06, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6f, 0x62, 0x6b, + 0x69, 0x74, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6f, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x2a, 0x3e, 0x0a, + 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, + 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, + 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x72, 0x61, + 0x63, 0x65, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x10, 0x04, 0x42, 0x1c, 0x5a, + 0x1a, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x69, 0x74, 0x7a, 0x2e, 0x74, 0x65, 0x63, 0x68, 0x2f, + 0x6d, 0x79, 0x61, 0x2f, 0x6f, 0x6b, 0x69, 0x74, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -710,14 +647,13 @@ var file_obkit_proto_depIdxs = []int32{ 2, // 2: obkit.Entry.timestamp:type_name -> obkit.Timestamp 0, // 3: obkit.Entry.kind:type_name -> obkit.Kind 3, // 4: obkit.Entry.tags:type_name -> obkit.Tag - 1, // 5: obkit.Entry.duration:type_name -> obkit.Duration - 5, // 6: obkit.Packet.client:type_name -> obkit.ClientInfo - 4, // 7: obkit.Packet.entries:type_name -> obkit.Entry - 8, // [8:8] is the sub-list for method output_type - 8, // [8:8] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 5, // 5: obkit.Packet.client:type_name -> obkit.ClientInfo + 4, // 6: obkit.Packet.entries:type_name -> obkit.Entry + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_obkit_proto_init() } @@ -808,11 +744,6 @@ func file_obkit_proto_init() { (*Tag_Duration)(nil), (*Tag_Timestamp)(nil), } - file_obkit_proto_msgTypes[3].OneofWrappers = []interface{}{ - (*Entry_String_)(nil), - (*Entry_Double)(nil), - (*Entry_Duration)(nil), - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/pb/obkit.pbext.go b/pb/obkit.pbext.go index 8803d72..bdb561c 100644 --- a/pb/obkit.pbext.go +++ b/pb/obkit.pbext.go @@ -29,20 +29,24 @@ import ( //go:generate protoc --go_out=paths=source_relative:. -I=. obkit.proto +// AsDuration converts a protocol level Duration to a Golang time.Duration. func (x *Duration) AsDuration() time.Duration { return time.Duration(x.Nanos) } +// DurationPB converts a time.Duration to a protocol level Duration. func DurationPB(d time.Duration) *Duration { return &Duration{ Nanos: int64(d), } } +// AsTime converts a protocol level Timestamp to a Golang time.Time. func (x *Timestamp) AsTime() time.Time { return time.Unix(x.Seconds, int64(x.Nanos)) } +// TimestampPB converts a time.Time to a protocol level Timestamp. func TimestampPB(t time.Time) *Timestamp { return &Timestamp{ Seconds: t.Unix(), @@ -50,7 +54,7 @@ func TimestampPB(t time.Time) *Timestamp { } } -// AsQueryString converts the associated tag to a URI query string +// AsQueryString converts the associated tag to a URI query string. func (x *Tag) AsQueryString() string { val := "" diff --git a/pb/obkit.proto b/pb/obkit.proto index c146407..ae5a148 100644 --- a/pb/obkit.proto +++ b/pb/obkit.proto @@ -79,13 +79,6 @@ message Entry { // Tags is an optional field that contains a list of metadata associated with the entry. repeated Tag tags = 4; - - // Value is an optional field that defines a value associated with the entry (e.g. in the case of a log, metric, or trace). - oneof value { - string string = 5; // used for logs - double double = 6; // used for metrics - Duration duration = 7; // used for traces - } } // ClientInfo is used to capture specific information about the client application that is emitting information. @@ -100,9 +93,12 @@ message ClientInfo { // Packet defines the set of data that is contained within each message sent to the server. message Packet { + // Id provides an id for the packet so the server can deduplicate packets of information being sent to them. + bytes id = 1; + // Client contains information that identifies the emitting client. - ClientInfo client = 1; + ClientInfo client = 2; // Entries are processed in batches, allowing local clients to buffer if necessary. - repeated Entry entries = 2; + repeated Entry entries = 3; }