Skip to content

Commit e9d0bbb

Browse files
feat: add OpenTelemetry support (#16)
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.
1 parent 47cf242 commit e9d0bbb

File tree

4 files changed

+118
-21
lines changed

4 files changed

+118
-21
lines changed

engine.go

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package easytemplate
66

77
import (
8+
"context"
89
"errors"
910
"fmt"
1011
"io/fs"
@@ -15,6 +16,10 @@ import (
1516
"github.com/speakeasy-api/easytemplate/internal/template"
1617
"github.com/speakeasy-api/easytemplate/internal/utils"
1718
"github.com/speakeasy-api/easytemplate/internal/vm"
19+
"go.opentelemetry.io/otel/attribute"
20+
"go.opentelemetry.io/otel/codes"
21+
"go.opentelemetry.io/otel/trace"
22+
"go.opentelemetry.io/otel/trace/noop"
1823
)
1924

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

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

102+
// WithTracer attaches an OpenTelemetry tracer to the engine and enables tracing support.
103+
func WithTracer(t trace.Tracer) Opt {
104+
return func(e *Engine) {
105+
e.tracer = t
106+
}
107+
}
108+
96109
// WithDebug enables debug mode for the engine, which will log additional information when errors occur.
97110
func WithDebug() Opt {
98111
return func(e *Engine) {
@@ -110,6 +123,8 @@ type Engine struct {
110123
ran bool
111124
jsFuncs map[string]func(call CallContext) goja.Value
112125
jsFiles map[string]string
126+
127+
tracer trace.Tracer
113128
}
114129

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

166+
if e.tracer == nil {
167+
e.tracer = noop.NewTracerProvider().Tracer("easytemplate")
168+
}
169+
151170
return e
152171
}
153172

154173
// RunScript runs the provided script file, with the provided data, starting the template engine and templating any templates triggered from the script.
155174
func (e *Engine) RunScript(scriptFile string, data any) error {
156-
vm, err := e.init(data)
175+
return e.RunScriptWithContext(context.Background(), scriptFile, data)
176+
}
177+
178+
// RunScriptWithContext runs the provided script file, with the provided data, starting the template engine and templating any templates triggered from the script.
179+
func (e *Engine) RunScriptWithContext(ctx context.Context, scriptFile string, data any) error {
180+
vm, err := e.init(ctx, data)
157181
if err != nil {
158182
return err
159183
}
@@ -172,7 +196,12 @@ func (e *Engine) RunScript(scriptFile string, data any) error {
172196

173197
// RunMethod enables calls to global template methods from easytemplate.
174198
func (e *Engine) RunMethod(scriptFile string, data any, fnName string, args ...any) (goja.Value, error) {
175-
vm, err := e.init(data)
199+
return e.RunMethodWithContext(context.Background(), scriptFile, data, fnName, args...)
200+
}
201+
202+
// RunMethodWithContext enables calls to global template methods from easytemplate.
203+
func (e *Engine) RunMethodWithContext(ctx context.Context, scriptFile string, data any, fnName string, args ...any) (goja.Value, error) {
204+
vm, err := e.init(ctx, data)
176205
if err != nil {
177206
return nil, err
178207
}
@@ -205,7 +234,12 @@ func (e *Engine) RunMethod(scriptFile string, data any, fnName string, args ...a
205234

206235
// RunTemplate runs the provided template file, with the provided data, starting the template engine and templating the provided template to a file.
207236
func (e *Engine) RunTemplate(templateFile string, outFile string, data any) error {
208-
vm, err := e.init(data)
237+
return e.RunTemplateWithContext(context.Background(), templateFile, outFile, data)
238+
}
239+
240+
// RunTemplateWithContext runs the provided template file, with the provided data, starting the template engine and templating the provided template to a file.
241+
func (e *Engine) RunTemplateWithContext(ctx context.Context, templateFile string, outFile string, data any) error {
242+
vm, err := e.init(ctx, data)
209243
if err != nil {
210244
return err
211245
}
@@ -215,7 +249,12 @@ func (e *Engine) RunTemplate(templateFile string, outFile string, data any) erro
215249

216250
// RunTemplateString runs the provided template file, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
217251
func (e *Engine) RunTemplateString(templateFile string, data any) (string, error) {
218-
vm, err := e.init(data)
252+
return e.RunTemplateStringWithContext(context.Background(), templateFile, data)
253+
}
254+
255+
// RunTemplateStringWithContext runs the provided template file, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
256+
func (e *Engine) RunTemplateStringWithContext(ctx context.Context, templateFile string, data any) (string, error) {
257+
vm, err := e.init(ctx, data)
219258
if err != nil {
220259
return "", err
221260
}
@@ -225,7 +264,12 @@ func (e *Engine) RunTemplateString(templateFile string, data any) (string, error
225264

226265
// RunTemplateStringInput runs the provided input template string, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
227266
func (e *Engine) RunTemplateStringInput(name, template string, data any) (string, error) {
228-
vm, err := e.init(data)
267+
return e.RunTemplateStringInputWithContext(context.Background(), name, template, data)
268+
}
269+
270+
// RunTemplateStringInputWithContext runs the provided input template string, with the provided data, starting the template engine and templating the provided template, returning the rendered result.
271+
func (e *Engine) RunTemplateStringInputWithContext(ctx context.Context, name, template string, data any) (string, error) {
272+
vm, err := e.init(ctx, data)
229273
if err != nil {
230274
return "", err
231275
}
@@ -234,7 +278,7 @@ func (e *Engine) RunTemplateStringInput(name, template string, data any) (string
234278
}
235279

236280
//nolint:funlen
237-
func (e *Engine) init(data any) (*vm.VM, error) {
281+
func (e *Engine) init(ctx context.Context, data any) (*vm.VM, error) {
238282
if e.ran {
239283
return nil, ErrAlreadyRan
240284
}
@@ -258,6 +302,7 @@ func (e *Engine) init(data any) (*vm.VM, error) {
258302
return fn(CallContext{
259303
FunctionCall: call,
260304
VM: v,
305+
Ctx: ctx,
261306
})
262307
}
263308
}(fn)
@@ -270,7 +315,20 @@ func (e *Engine) init(data any) (*vm.VM, error) {
270315
// This need to have the vm passed in so that the functions can be called
271316
e.templator.TmplFuncs["templateFile"] = func(v *vm.VM) func(string, string, any) (string, error) {
272317
return func(templateFile, outFile string, data any) (string, error) {
273-
err := e.templator.TemplateFile(v, templateFile, outFile, data)
318+
var err error
319+
_, span := e.tracer.Start(ctx, "templateFile", trace.WithAttributes(
320+
attribute.String("templateFile", templateFile),
321+
attribute.String("outFile", outFile),
322+
))
323+
defer func() {
324+
span.RecordError(err)
325+
if err != nil {
326+
span.SetStatus(codes.Error, err.Error())
327+
}
328+
span.End()
329+
}()
330+
331+
err = e.templator.TemplateFile(v, templateFile, outFile, data)
274332
if err != nil {
275333
return "", err
276334
}

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ require (
88
github.com/evanw/esbuild v0.19.11
99
github.com/go-sourcemap/sourcemap v2.1.3+incompatible
1010
github.com/golang/mock v1.6.0
11-
github.com/stretchr/testify v1.8.1
11+
github.com/stretchr/testify v1.8.4
12+
go.opentelemetry.io/otel v1.24.0
13+
go.opentelemetry.io/otel/trace v1.24.0
1214
)
1315

1416
require (

go.sum

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

templating.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,73 @@ package easytemplate
22

33
import (
44
"github.com/dop251/goja"
5+
"go.opentelemetry.io/otel/attribute"
6+
"go.opentelemetry.io/otel/codes"
7+
"go.opentelemetry.io/otel/trace"
58
)
69

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

10-
if err := e.templator.TemplateFile(call.VM, call.Argument(0).String(), call.Argument(1).String(), inputData); err != nil {
15+
ctx := call.Ctx
16+
_, span := e.tracer.Start(ctx, "js:templateFile", trace.WithAttributes(
17+
attribute.String("templateFile", templateFile),
18+
attribute.String("outFile", outFile),
19+
))
20+
defer span.End()
21+
22+
if err := e.templator.TemplateFile(call.VM, templateFile, outFile, inputData); err != nil {
23+
span.RecordError(err)
24+
span.SetStatus(codes.Error, err.Error())
25+
span.End()
26+
1127
panic(call.VM.NewGoError(err))
1228
}
1329

1430
return goja.Undefined()
1531
}
1632

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

20-
output, err := e.templator.TemplateString(call.VM, call.Argument(0).String(), inputData)
37+
ctx := call.Ctx
38+
_, span := e.tracer.Start(ctx, "js:templateString", trace.WithAttributes(
39+
attribute.String("templateFile", templateFile),
40+
))
41+
defer span.End()
42+
43+
output, err := e.templator.TemplateString(call.VM, templateFile, inputData)
2144
if err != nil {
45+
span.RecordError(err)
46+
span.SetStatus(codes.Error, err.Error())
47+
span.End()
48+
2249
panic(call.VM.NewGoError(err))
2350
}
2451

2552
return call.VM.ToValue(output)
2653
}
2754

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

31-
output, err := e.templator.TemplateStringInput(call.VM, call.Argument(0).String(), call.Argument(1).String(), inputData)
60+
ctx := call.Ctx
61+
_, span := e.tracer.Start(ctx, "js:templateStringInput", trace.WithAttributes(
62+
attribute.String("name", name),
63+
))
64+
defer span.End()
65+
66+
output, err := e.templator.TemplateStringInput(call.VM, name, input, inputData)
3267
if err != nil {
68+
span.RecordError(err)
69+
span.SetStatus(codes.Error, err.Error())
70+
span.End()
71+
3372
panic(call.VM.NewGoError(err))
3473
}
3574

0 commit comments

Comments
 (0)