193 lines
4.6 KiB
Go
193 lines
4.6 KiB
Go
|
// Copyright (C) 2022 Mya Pitzeruse
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Affero General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Affero General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Affero General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
//
|
||
|
|
||
|
package okit
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"go.pitz.tech/okit/pb"
|
||
|
)
|
||
|
|
||
|
// Client provides a default implementation.
|
||
|
type Client struct {
|
||
|
now func() time.Time
|
||
|
uuid func() string
|
||
|
tags []Tag
|
||
|
}
|
||
|
|
||
|
func (o Client) withCaller() Client {
|
||
|
pkg, fn, line := infer(caller(3))
|
||
|
|
||
|
o.tags = append(o.tags,
|
||
|
String("package", pkg),
|
||
|
String("func", fn),
|
||
|
Int("line", line),
|
||
|
)
|
||
|
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
// With adds additional tags to the observer instance and returns a new copy for reference.
|
||
|
func (o Client) With(tags ...Tag) Client {
|
||
|
o.tags = append(o.tags, tags...)
|
||
|
|
||
|
return o
|
||
|
}
|
||
|
|
||
|
// 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.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Kind: pb.Kind_Event,
|
||
|
Name: event,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Observe reports a metric and it's associated value.
|
||
|
func (o Client) Observe(metric string, value float64, tags ...Tag) {
|
||
|
o.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Value: &pb.Entry_Double{Double: value},
|
||
|
Kind: pb.Kind_Metric,
|
||
|
Name: 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) {
|
||
|
pkg, fn, _ := infer(caller(2))
|
||
|
name := pkg + "." + fn
|
||
|
|
||
|
start := o.now()
|
||
|
|
||
|
span := Span{
|
||
|
TraceID: o.uuid(),
|
||
|
ID: o.uuid(),
|
||
|
}
|
||
|
|
||
|
v := ctx.Value(spanKey)
|
||
|
if v != nil {
|
||
|
parent, hasParent := v.(Span)
|
||
|
if hasParent {
|
||
|
span.TraceID = parent.TraceID
|
||
|
span.ParentID = parent.ID
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return context.WithValue(ctx, spanKey, span), func() {
|
||
|
duration := o.now().Sub(start)
|
||
|
|
||
|
tags = append(
|
||
|
[]Tag{
|
||
|
String("trace-id", span.TraceID),
|
||
|
String("trace-span-id", span.ID),
|
||
|
String("trace-parent-id", span.ParentID),
|
||
|
},
|
||
|
tags...,
|
||
|
)
|
||
|
|
||
|
o.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Kind: pb.Kind_Trace,
|
||
|
Name: name,
|
||
|
Value: &pb.Entry_Duration{Duration: pb.DurationPB(duration)},
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Debug produces a debug log event.
|
||
|
func (o Client) Debug(msg string, tags ...Tag) {
|
||
|
// todo: is debug enabled?
|
||
|
|
||
|
o.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Kind: pb.Kind_Log,
|
||
|
Name: msg,
|
||
|
Value: &pb.Entry_String_{String_: "debug"},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Info produces an information log event.
|
||
|
func (o Client) Info(msg string, tags ...Tag) {
|
||
|
// todo: is info enabled?
|
||
|
|
||
|
o.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Kind: pb.Kind_Log,
|
||
|
Name: msg,
|
||
|
Value: &pb.Entry_String_{String_: "info"},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Warn produces a warning that is surfaced to operators.
|
||
|
func (o Client) Warn(msg string, tags ...Tag) {
|
||
|
o.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Kind: pb.Kind_Log,
|
||
|
Name: msg,
|
||
|
Value: &pb.Entry_String_{String_: "warn"},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Error produces a message that communicates an error has occurred.
|
||
|
func (o Client) Error(msg string, tags ...Tag) {
|
||
|
o.withCaller().With(tags...).emit(&pb.Entry{
|
||
|
Kind: pb.Kind_Log,
|
||
|
Name: msg,
|
||
|
Value: &pb.Entry_String_{String_: "error"},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (o Client) emit(entry *pb.Entry) {
|
||
|
for _, tag := range o.tags {
|
||
|
if tagpb := tag.AsTagPB(); tagpb != nil {
|
||
|
entry.Tags = append(entry.Tags, tagpb)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// todo: send to sinks
|
||
|
}
|
||
|
|
||
|
// max returns the largest number in a series.
|
||
|
func max(series ...int) int {
|
||
|
largest := 0
|
||
|
|
||
|
for _, v := range series {
|
||
|
if v > largest {
|
||
|
largest = v
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return largest
|
||
|
}
|
||
|
|
||
|
// caller attempts to get method caller information. This information is used for tracing information across an
|
||
|
// applications source code.
|
||
|
func caller(skip int) (name string, line int) {
|
||
|
pctr, _, line, ok := runtime.Caller(skip)
|
||
|
if !ok {
|
||
|
return "", 0
|
||
|
}
|
||
|
|
||
|
return runtime.FuncForPC(pctr).Name(), line
|
||
|
}
|
||
|
|
||
|
// infer attempts to determine the calling package and function using runtime information.
|
||
|
func infer(name string, line int) (pkg, fn string, l int) {
|
||
|
lastSlash := max(0, strings.LastIndexByte(name, '/'))
|
||
|
lastDot := lastSlash + strings.LastIndexByte(name[lastSlash:], '.')
|
||
|
|
||
|
return name[:lastDot], name[lastDot+1:], line
|
||
|
}
|