mirror of
https://github.com/foomo/gofuncy.git
synced 2025-10-16 12:25:41 +00:00
142 lines
3.1 KiB
Go
142 lines
3.1 KiB
Go
package gofuncy
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/Ju0x/humanhash"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/metric"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
type (
|
|
Options struct {
|
|
l *slog.Logger
|
|
ctx context.Context //nolint:containedctx // required
|
|
name string
|
|
// telemetry
|
|
meter metric.Meter
|
|
tracer trace.Tracer
|
|
counter metric.Int64UpDownCounter
|
|
counterName string
|
|
histogram metric.Int64Histogram
|
|
histogramName string
|
|
telemetryEnabled bool
|
|
}
|
|
Option func(*Options)
|
|
)
|
|
|
|
func WithName(name string) Option {
|
|
return func(o *Options) {
|
|
o.name = name
|
|
}
|
|
}
|
|
|
|
func WithContext(ctx context.Context) Option {
|
|
return func(o *Options) {
|
|
o.ctx = ctx
|
|
}
|
|
}
|
|
|
|
func WithTelemetryEnabled(enabled bool) Option {
|
|
return func(o *Options) {
|
|
o.telemetryEnabled = enabled
|
|
}
|
|
}
|
|
|
|
func WithMeter(meter metric.Meter) Option {
|
|
return func(o *Options) {
|
|
o.meter = meter
|
|
}
|
|
}
|
|
|
|
func WithCounterName(name string) Option {
|
|
return func(o *Options) {
|
|
o.counterName = name
|
|
}
|
|
}
|
|
|
|
func WithHistogramName(name string) Option {
|
|
return func(o *Options) {
|
|
o.histogramName = name
|
|
}
|
|
}
|
|
|
|
func Go(fn Func, opts ...Option) <-chan error {
|
|
o := &Options{
|
|
l: slog.Default(),
|
|
counterName: "gofuncy.routine.count",
|
|
histogramName: "gofuncy.routine.duration",
|
|
telemetryEnabled: os.Getenv("OTEL_ENABLED") == "true",
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
if opt != nil {
|
|
opt(o)
|
|
}
|
|
}
|
|
|
|
if o.ctx == nil {
|
|
o.ctx = context.Background()
|
|
}
|
|
if o.name == "" {
|
|
if _, file, line, ok := runtime.Caller(0); ok {
|
|
h := sha256.New()
|
|
_, _ = fmt.Fprintf(h, "%s:%d", file, line)
|
|
o.name, _ = humanhash.Humanize(h.Sum(nil), 2, "-")
|
|
}
|
|
}
|
|
// create telemetry if enabled
|
|
if o.telemetryEnabled {
|
|
if o.meter == nil {
|
|
o.meter = otel.Meter("gofuncy")
|
|
}
|
|
if o.tracer == nil {
|
|
o.tracer = otel.Tracer("gofuncy")
|
|
}
|
|
if value, err := o.meter.Int64UpDownCounter(o.counterName); err != nil {
|
|
o.l.Error("failed to initialize gauge", "error", err)
|
|
} else {
|
|
o.counter = value
|
|
}
|
|
if value, err := o.meter.Int64Histogram(o.histogramName); err != nil {
|
|
o.l.Error("failed to initialize histogram", "error", err)
|
|
} else {
|
|
o.histogram = value
|
|
}
|
|
}
|
|
|
|
err := make(chan error)
|
|
go func(o *Options, err chan<- error) {
|
|
defer close(err)
|
|
ctx := o.ctx
|
|
var span trace.Span
|
|
if o.tracer != nil {
|
|
ctx, span = o.tracer.Start(o.ctx, o.name)
|
|
defer span.End()
|
|
}
|
|
// create telemetry if enabled
|
|
if o.counter != nil {
|
|
o.counter.Add(ctx, 1, metric.WithAttributes(semconv.ProcessRuntimeName(o.name)))
|
|
defer o.counter.Add(ctx, -1, metric.WithAttributes(semconv.ProcessRuntimeName(o.name)))
|
|
}
|
|
if o.histogram != nil {
|
|
start := time.Now()
|
|
defer func() {
|
|
o.histogram.Record(ctx, time.Since(start).Milliseconds(), metric.WithAttributes(semconv.ProcessRuntimeName(o.name)))
|
|
}()
|
|
}
|
|
ctx = injectParentRoutineIntoContext(ctx, RoutineFromContext(ctx))
|
|
ctx = injectRoutineIntoContext(ctx, o.name)
|
|
err <- fn(ctx)
|
|
}(o, err)
|
|
return err
|
|
}
|