Skip to content

Commit

Permalink
feat: add OpenTelemetry support (#16)
Browse files Browse the repository at this point in the history
This change adds support for tracing templating operations and function
calls using an OpenTelemetry tracer that is provided as an option to the
easytemplate engine.
  • Loading branch information
disintegrator authored Feb 27, 2024
1 parent 47cf242 commit e9d0bbb
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 21 deletions.
74 changes: 66 additions & 8 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package easytemplate

import (
"context"
"errors"
"fmt"
"io/fs"
Expand All @@ -15,6 +16,10 @@ import (
"github.com/speakeasy-api/easytemplate/internal/template"
"github.com/speakeasy-api/easytemplate/internal/utils"
"github.com/speakeasy-api/easytemplate/internal/vm"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)

var (
Expand All @@ -33,7 +38,8 @@ var (
// CallContext is the context that is passed to go functions when called from js.
type CallContext struct {
goja.FunctionCall
VM *vm.VM
VM *vm.VM
Ctx context.Context //nolint:containedctx // runtime context is necessarily stored in a struct as it jumps from Go to JS.
}

// Opt is a function that configures the Engine.
Expand Down Expand Up @@ -93,6 +99,13 @@ func WithJSFiles(files map[string]string) Opt {
}
}

// WithTracer attaches an OpenTelemetry tracer to the engine and enables tracing support.
func WithTracer(t trace.Tracer) Opt {
return func(e *Engine) {
e.tracer = t
}
}

// WithDebug enables debug mode for the engine, which will log additional information when errors occur.
func WithDebug() Opt {
return func(e *Engine) {
Expand All @@ -110,6 +123,8 @@ type Engine struct {
ran bool
jsFuncs map[string]func(call CallContext) goja.Value
jsFiles map[string]string

tracer trace.Tracer
}

// New creates a new Engine with the provided options.
Expand Down Expand Up @@ -148,12 +163,21 @@ func New(opts ...Opt) *Engine {
opt(e)
}

if e.tracer == nil {
e.tracer = noop.NewTracerProvider().Tracer("easytemplate")
}

return e
}

// RunScript runs the provided script file, with the provided data, starting the template engine and templating any templates triggered from the script.
func (e *Engine) RunScript(scriptFile string, data any) error {
vm, err := e.init(data)
return e.RunScriptWithContext(context.Background(), scriptFile, data)
}

// RunScriptWithContext runs the provided script file, with the provided data, starting the template engine and templating any templates triggered from the script.
func (e *Engine) RunScriptWithContext(ctx context.Context, scriptFile string, data any) error {
vm, err := e.init(ctx, data)
if err != nil {
return err
}
Expand All @@ -172,7 +196,12 @@ func (e *Engine) RunScript(scriptFile string, data any) error {

// RunMethod enables calls to global template methods from easytemplate.
func (e *Engine) RunMethod(scriptFile string, data any, fnName string, args ...any) (goja.Value, error) {
vm, err := e.init(data)
return e.RunMethodWithContext(context.Background(), scriptFile, data, fnName, args...)
}

// RunMethodWithContext enables calls to global template methods from easytemplate.
func (e *Engine) RunMethodWithContext(ctx context.Context, scriptFile string, data any, fnName string, args ...any) (goja.Value, error) {
vm, err := e.init(ctx, data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -205,7 +234,12 @@ func (e *Engine) RunMethod(scriptFile string, data any, fnName string, args ...a

// RunTemplate runs the provided template file, with the provided data, starting the template engine and templating the provided template to a file.
func (e *Engine) RunTemplate(templateFile string, outFile string, data any) error {
vm, err := e.init(data)
return e.RunTemplateWithContext(context.Background(), templateFile, outFile, data)
}

// RunTemplateWithContext runs the provided template file, with the provided data, starting the template engine and templating the provided template to a file.
func (e *Engine) RunTemplateWithContext(ctx context.Context, templateFile string, outFile string, data any) error {
vm, err := e.init(ctx, data)
if err != nil {
return err
}
Expand All @@ -215,7 +249,12 @@ func (e *Engine) RunTemplate(templateFile string, outFile string, data any) erro

// RunTemplateString runs the provided template file, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
func (e *Engine) RunTemplateString(templateFile string, data any) (string, error) {
vm, err := e.init(data)
return e.RunTemplateStringWithContext(context.Background(), templateFile, data)
}

// RunTemplateStringWithContext runs the provided template file, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
func (e *Engine) RunTemplateStringWithContext(ctx context.Context, templateFile string, data any) (string, error) {
vm, err := e.init(ctx, data)
if err != nil {
return "", err
}
Expand All @@ -225,7 +264,12 @@ func (e *Engine) RunTemplateString(templateFile string, data any) (string, error

// RunTemplateStringInput runs the provided input template string, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
func (e *Engine) RunTemplateStringInput(name, template string, data any) (string, error) {
vm, err := e.init(data)
return e.RunTemplateStringInputWithContext(context.Background(), name, template, data)
}

// RunTemplateStringInputWithContext runs the provided input template string, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
func (e *Engine) RunTemplateStringInputWithContext(ctx context.Context, name, template string, data any) (string, error) {
vm, err := e.init(ctx, data)
if err != nil {
return "", err
}
Expand All @@ -234,7 +278,7 @@ func (e *Engine) RunTemplateStringInput(name, template string, data any) (string
}

//nolint:funlen
func (e *Engine) init(data any) (*vm.VM, error) {
func (e *Engine) init(ctx context.Context, data any) (*vm.VM, error) {
if e.ran {
return nil, ErrAlreadyRan
}
Expand All @@ -258,6 +302,7 @@ func (e *Engine) init(data any) (*vm.VM, error) {
return fn(CallContext{
FunctionCall: call,
VM: v,
Ctx: ctx,
})
}
}(fn)
Expand All @@ -270,7 +315,20 @@ func (e *Engine) init(data any) (*vm.VM, error) {
// This need to have the vm passed in so that the functions can be called
e.templator.TmplFuncs["templateFile"] = func(v *vm.VM) func(string, string, any) (string, error) {
return func(templateFile, outFile string, data any) (string, error) {
err := e.templator.TemplateFile(v, templateFile, outFile, data)
var err error
_, span := e.tracer.Start(ctx, "templateFile", trace.WithAttributes(
attribute.String("templateFile", templateFile),
attribute.String("outFile", outFile),
))
defer func() {
span.RecordError(err)
if err != nil {
span.SetStatus(codes.Error, err.Error())
}
span.End()
}()

err = e.templator.TemplateFile(v, templateFile, outFile, data)
if err != nil {
return "", err
}
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ require (
github.com/evanw/esbuild v0.19.11
github.com/go-sourcemap/sourcemap v2.1.3+incompatible
github.com/golang/mock v1.6.0
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
)

require (
Expand Down
16 changes: 7 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4M
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
Expand All @@ -22,6 +21,7 @@ github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyL
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
Expand All @@ -38,15 +38,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand Down Expand Up @@ -95,6 +94,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
45 changes: 42 additions & 3 deletions templating.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,73 @@ package easytemplate

import (
"github.com/dop251/goja"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)

func (e *Engine) templateFileJS(call CallContext) goja.Value {
templateFile := call.Argument(0).String()
outFile := call.Argument(1).String()
inputData := call.Argument(2).Export() //nolint:gomnd

if err := e.templator.TemplateFile(call.VM, call.Argument(0).String(), call.Argument(1).String(), inputData); err != nil {
ctx := call.Ctx
_, span := e.tracer.Start(ctx, "js:templateFile", trace.WithAttributes(
attribute.String("templateFile", templateFile),
attribute.String("outFile", outFile),
))
defer span.End()

if err := e.templator.TemplateFile(call.VM, templateFile, outFile, inputData); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.End()

panic(call.VM.NewGoError(err))
}

return goja.Undefined()
}

func (e *Engine) templateStringJS(call CallContext) goja.Value {
templateFile := call.Argument(0).String()
inputData := call.Argument(1).Export()

output, err := e.templator.TemplateString(call.VM, call.Argument(0).String(), inputData)
ctx := call.Ctx
_, span := e.tracer.Start(ctx, "js:templateString", trace.WithAttributes(
attribute.String("templateFile", templateFile),
))
defer span.End()

output, err := e.templator.TemplateString(call.VM, templateFile, inputData)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.End()

panic(call.VM.NewGoError(err))
}

return call.VM.ToValue(output)
}

func (e *Engine) templateStringInputJS(call CallContext) goja.Value {
name := call.Argument(0).String()
input := call.Argument(1).String()
inputData := call.Argument(2).Export() //nolint:gomnd

output, err := e.templator.TemplateStringInput(call.VM, call.Argument(0).String(), call.Argument(1).String(), inputData)
ctx := call.Ctx
_, span := e.tracer.Start(ctx, "js:templateStringInput", trace.WithAttributes(
attribute.String("name", name),
))
defer span.End()

output, err := e.templator.TemplateStringInput(call.VM, name, input, inputData)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
span.End()

panic(call.VM.NewGoError(err))
}

Expand Down

0 comments on commit e9d0bbb

Please sign in to comment.