newrelic / go-agent

New Relic Go Agent
Apache License 2.0
775 stars 295 forks source link

Wrapper helpers using reflection #970

Open ktsivkov opened 1 month ago

ktsivkov commented 1 month ago

Provide helper functions to enable easier and cleaner way to start transactions and segments.

Summary

By my experience most of the time the applications that require transactions they are quite straight forward.

Desired Behaviour

Optimally will be nice to have a way to wrap application specific functions that when invoked will start transactions and segments without coupling the application code to the agent's behavior, and without changing their underlying type.

To achieve the wrapping behavior we can use reflections, and to avoid changing the underlying type of the "wrapees" we can use generics.

Possible Solution

I can think of two solutions, the easiest one could be the addition of helper functions WrapTransactionFunc and WrapSegmentFunc, and they can be implemented like this:

func WrapTransactionFunc[V any](app *Application, wrapee V, transactionName string, traceOptions ...TraceOption) V {
    typ := reflect.TypeOf(wrapee)
    if typ.Kind() != reflect.Func {
        panic("wrapee must be a func")
    }

    wrappedFunc := func(args []reflect.Value) []reflect.Value {
        tx := app.StartTransaction(transactionName, traceOptions...)
        defer tx.End()

        return reflect.ValueOf(wrapee).Call(args)
    }

    fn := reflect.MakeFunc(typ, wrappedFunc)
    return fn.Interface().(V)
}

func WrapSegmentFunc[V any](tx *Transaction, wrapee V, segmentName string) V {
    typ := reflect.TypeOf(wrapee)
    if typ.Kind() != reflect.Func {
        panic("wrapee must be a func")
    }

    wrappedFunc := func(args []reflect.Value) []reflect.Value {
        sg := tx.StartSegment(segmentName)
        defer sg.End()

        return reflect.ValueOf(wrapee).Call(args)
    }

    fn := reflect.MakeFunc(typ, wrappedFunc)
    return fn.Interface().(V)
}
iamemilio commented 1 month ago

This would rack up quite a bit of overhead. Have you played with easy-instrumentation at all?

ktsivkov commented 1 month ago

@iamemilio No, but just checked the readme file. It looks quite interesting, I will definitely have a look!

iamemilio commented 1 month ago

Don't hesitate to leave us some feedback! We don't have the ability to exclude functions at the moment, but can track that as a possible feature.