Skip to content

Commit c83fbaa

Browse files
authored
feat(go): add more tracing details (#807)
1 parent e01e267 commit c83fbaa

File tree

2 files changed

+100
-25
lines changed

2 files changed

+100
-25
lines changed

pkg/api/render.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ import (
99
"net/url"
1010
"regexp"
1111
"strconv"
12+
"strings"
1213
"time"
1314
_ "time/tzdata" // fallback where we have no tzdata on the distro; used in LoadLocation
1415

1516
"github.com/grafana/grafana-image-renderer/pkg/service"
1617
"github.com/prometheus/client_golang/prometheus"
18+
"go.opentelemetry.io/otel/attribute"
19+
"go.opentelemetry.io/otel/codes"
20+
"go.opentelemetry.io/otel/trace"
1721
)
1822

1923
var (
@@ -37,39 +41,52 @@ func HandleGetRender(browser *service.BrowserService) http.Handler {
3741

3842
rawTargetURL := r.URL.Query().Get("url")
3943
if rawTargetURL == "" {
44+
span.SetStatus(codes.Error, "url query param empty")
4045
http.Error(w, "missing 'url' query parameter", http.StatusBadRequest)
4146
return
4247
}
4348
targetURL, err := url.Parse(rawTargetURL)
4449
if err != nil {
50+
span.SetStatus(codes.Error, "url query param was unparseable")
51+
span.RecordError(err, trace.WithAttributes(attribute.String("url", rawTargetURL)))
4552
http.Error(w, fmt.Sprintf("invalid 'url' query parameter: %v", err), http.StatusBadRequest)
4653
return
4754
}
55+
span.SetAttributes(attribute.String("url", targetURL.String()))
56+
4857
var options []service.RenderingOption
4958

5059
width, height := -1, -1
5160
if widthStr := r.URL.Query().Get("width"); widthStr != "" {
5261
var err error
5362
width, err = strconv.Atoi(widthStr)
5463
if err != nil {
64+
span.SetStatus(codes.Error, "invalid width query param")
65+
span.RecordError(err, trace.WithAttributes(attribute.String("width", widthStr)))
5566
http.Error(w, fmt.Sprintf("invalid 'width' query parameter: %v", err), http.StatusBadRequest)
5667
return
5768
}
69+
span.SetAttributes(attribute.Int("width", width))
5870
}
5971
if heightStr := r.URL.Query().Get("height"); heightStr != "" {
6072
var err error
6173
height, err = strconv.Atoi(heightStr)
6274
if err != nil {
75+
span.SetStatus(codes.Error, "invalid height query param")
76+
span.RecordError(err, trace.WithAttributes(attribute.String("height", heightStr)))
6377
http.Error(w, fmt.Sprintf("invalid 'height' query parameter: %v", err), http.StatusBadRequest)
6478
return
6579
}
80+
span.SetAttributes(attribute.Int("height", height))
6681
}
6782
options = append(options, service.WithViewport(width, height))
6883
if timeout := r.URL.Query().Get("timeout"); timeout != "" {
6984
var dur time.Duration
7085
if regexpOnlyNumbers.MatchString(timeout) {
7186
seconds, err := strconv.Atoi(timeout)
7287
if err != nil {
88+
span.SetStatus(codes.Error, "invalid timeout query param (Atoi)")
89+
span.RecordError(err, trace.WithAttributes(attribute.String("timeout", timeout)))
7390
http.Error(w, fmt.Sprintf("invalid 'timeout' query parameter: %v", err), http.StatusBadRequest)
7491
return
7592
}
@@ -78,43 +95,54 @@ func HandleGetRender(browser *service.BrowserService) http.Handler {
7895
var err error
7996
dur, err = time.ParseDuration(timeout)
8097
if err != nil {
98+
span.SetStatus(codes.Error, "invalid timeout query param (ParseDuration)")
99+
span.RecordError(err, trace.WithAttributes(attribute.String("timeout", timeout)))
81100
http.Error(w, fmt.Sprintf("invalid 'timeout' query parameter: %v", err), http.StatusBadRequest)
82101
return
83102
}
84103
}
85104
if dur > 0 {
105+
span.SetAttributes(attribute.String("timeout", dur.String()))
86106
timeoutCtx, cancelTimeout := context.WithTimeout(ctx, dur)
87107
defer cancelTimeout()
88108
ctx = timeoutCtx
89109
}
90110
}
91111
if scaleFactor := r.URL.Query().Get("deviceScaleFactor"); scaleFactor != "" {
92-
scaleFactor, err := strconv.ParseFloat(scaleFactor, 64)
112+
pageScaleFactor, err := strconv.ParseFloat(scaleFactor, 64)
93113
if err != nil {
114+
span.SetStatus(codes.Error, "invalid deviceScaleFactor query param")
115+
span.RecordError(err, trace.WithAttributes(attribute.String("scaleFactor", scaleFactor)))
94116
http.Error(w, fmt.Sprintf("invalid 'deviceScaleFactor' query parameter: %v", err), http.StatusBadRequest)
95117
return
96118
}
97-
options = append(options, service.WithPageScaleFactor(scaleFactor))
119+
options = append(options, service.WithPageScaleFactor(pageScaleFactor))
120+
span.SetAttributes(attribute.Float64("deviceScaleFactor", pageScaleFactor))
98121
}
99122
if timeZone := r.URL.Query().Get("timeZone"); timeZone != "" {
100-
timeZone, err := time.LoadLocation(timeZone)
123+
timeLocation, err := time.LoadLocation(timeZone)
101124
if err != nil {
125+
span.SetStatus(codes.Error, "invalid timeZone query param")
126+
span.RecordError(err, trace.WithAttributes(attribute.String("timeZone", timeZone)))
102127
http.Error(w, fmt.Sprintf("invalid 'timeZone' query parameter: %v", err), http.StatusBadRequest)
103128
return
104129
}
105-
options = append(options, service.WithTimeZone(timeZone))
130+
options = append(options, service.WithTimeZone(timeLocation))
131+
span.SetAttributes(attribute.String("timeZone", timeZone))
106132
}
107133
if landscape := r.URL.Query().Get("landscape"); landscape != "" {
108134
options = append(options, service.WithLandscape(landscape == "true"))
135+
span.SetAttributes(attribute.Bool("landscape", landscape == "true"))
109136
}
110137
renderKey := r.URL.Query().Get("renderKey")
111138
domain := r.URL.Query().Get("domain")
112139
if renderKey != "" && domain != "" {
113140
options = append(options, service.WithCookie("renderKey", renderKey, domain))
141+
span.AddEvent("added renderKey cookie", trace.WithAttributes(attribute.String("domain", domain)))
114142
}
115143
encoding := r.URL.Query().Get("encoding")
116144
var printer service.Printer
117-
switch encoding {
145+
switch strings.ToLower(encoding) {
118146
case "", "pdf":
119147
var printerOpts []service.PDFPrinterOption
120148

@@ -126,10 +154,13 @@ func HandleGetRender(browser *service.BrowserService) http.Handler {
126154
if paper != "" {
127155
var psz service.PaperSize
128156
if err := psz.UnmarshalText([]byte(paper)); err != nil {
157+
span.SetStatus(codes.Error, "invalid pdf.format query param")
158+
span.RecordError(err, trace.WithAttributes(attribute.String("pdf.format", paper)))
129159
http.Error(w, fmt.Sprintf("invalid 'pdf.format' query parameter: %v", err), http.StatusBadRequest)
130160
return
131161
}
132162
printerOpts = append(printerOpts, service.WithPaperSize(psz))
163+
span.SetAttributes(attribute.String("pdf.format", paper))
133164
}
134165

135166
printBackground := r.URL.Query().Get("pdf.printBackground")
@@ -139,6 +170,7 @@ func HandleGetRender(browser *service.BrowserService) http.Handler {
139170
}
140171
if printBackground != "" {
141172
printerOpts = append(printerOpts, service.WithPrintingBackground(printBackground == "true"))
173+
span.SetAttributes(attribute.Bool("pdf.printBackground", printBackground == "true"))
142174
}
143175

144176
pageRanges := r.URL.Query().Get("pdf.pageRanges")
@@ -148,43 +180,57 @@ func HandleGetRender(browser *service.BrowserService) http.Handler {
148180
}
149181
if pageRanges != "" {
150182
printerOpts = append(printerOpts, service.WithPageRanges(pageRanges))
183+
span.SetAttributes(attribute.String("pdf.pageRanges", pageRanges))
151184
}
152185

153186
var err error
154187
printer, err = service.NewPDFPrinter(printerOpts...)
155188
if err != nil {
189+
span.SetStatus(codes.Error, "invalid pdf printer option")
190+
span.RecordError(err)
156191
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
157192
return
158193
}
194+
span.SetAttributes(attribute.String("encoding", "pdf"))
159195

160196
if pdfLandscape := r.URL.Query().Get("pdfLandscape"); pdfLandscape != "" {
161197
options = append(options, service.WithLandscape(pdfLandscape == "true"))
198+
span.SetAttributes(attribute.Bool("pdfLandscape", pdfLandscape == "true"))
162199
}
163200
case "png":
164201
var printerOpts []service.PNGPrinterOption
165202
if height == -1 {
166203
printerOpts = append(printerOpts, service.WithFullHeight(true))
167204
options = append(options, service.WithViewport(width, 1080)) // add some height to make scrolling faster
205+
span.SetAttributes(attribute.Bool("fullHeight", true))
168206
}
169207

170208
var err error
171209
printer, err = service.NewPNGPrinter(printerOpts...)
172210
if err != nil {
211+
span.SetStatus(codes.Error, "invalid png printer option")
212+
span.RecordError(err)
173213
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
174214
return
175215
}
216+
span.SetAttributes(attribute.String("encoding", "png"))
176217
default:
218+
span.SetStatus(codes.Error, "invalid encoding query param")
219+
span.RecordError(errors.New("invalid encoding"), trace.WithAttributes(attribute.String("encoding", encoding)))
177220
http.Error(w, fmt.Sprintf("invalid 'encoding' query parameter: %q", encoding), http.StatusBadRequest)
178221
return
179222
}
180223
if acceptLanguage := r.Header.Get("Accept-Language"); acceptLanguage != "" {
181224
options = append(options, service.WithHeader("Accept-Language", acceptLanguage))
225+
span.SetAttributes(attribute.String("Accept-Language", acceptLanguage))
182226
}
183227

184228
start := time.Now()
185229
body, contentType, err := browser.Render(ctx, rawTargetURL, printer, options...)
186230
if err != nil {
187231
MetricRenderDuration.WithLabelValues("error").Observe(time.Since(start).Seconds())
232+
span.SetStatus(codes.Error, "rendering failed")
233+
span.RecordError(err)
188234
if errors.Is(err, context.DeadlineExceeded) ||
189235
errors.Is(err, context.Canceled) {
190236
http.Error(w, "Request timed out", http.StatusRequestTimeout)
@@ -198,6 +244,7 @@ func HandleGetRender(browser *service.BrowserService) http.Handler {
198244
return
199245
}
200246
MetricRenderDuration.WithLabelValues("success").Observe(time.Since(start).Seconds())
247+
span.SetStatus(codes.Ok, "rendered successfully")
201248

202249
w.Header().Set("Content-Type", contentType)
203250
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))

pkg/api/rendercsv.go

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ package api
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"log/slog"
78
"net/http"
89
"strconv"
10+
"strings"
911
"time"
1012

1113
"github.com/grafana/grafana-image-renderer/pkg/service"
1214
"github.com/prometheus/client_golang/prometheus"
15+
"go.opentelemetry.io/otel/attribute"
16+
"go.opentelemetry.io/otel/codes"
17+
"go.opentelemetry.io/otel/trace"
1318
)
1419

1520
var (
@@ -24,50 +29,73 @@ var (
2429

2530
func HandleGetRenderCSV(browser *service.BrowserService) http.Handler {
2631
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32+
tracer := tracer(r.Context())
33+
ctx, span := tracer.Start(r.Context(), "HandleGetRenderCSV")
34+
defer span.End()
35+
r = r.WithContext(ctx)
36+
2737
url := r.URL.Query().Get("url")
2838
if url == "" {
39+
span.SetStatus(codes.Error, "url query param empty")
2940
http.Error(w, "missing 'url' query parameter", http.StatusBadRequest)
3041
return
3142
}
32-
if encoding := r.URL.Query().Get("encoding"); encoding != "" && encoding != "csv" {
43+
span.SetAttributes(attribute.String("url", url))
44+
45+
if encoding := r.URL.Query().Get("encoding"); encoding != "" && !strings.EqualFold(encoding, "csv") {
46+
span.SetStatus(codes.Error, "invalid encoding query param")
47+
span.SetAttributes(attribute.String("encoding", encoding))
3348
http.Error(w, "invalid 'encoding' query parameter: must be 'csv' or empty/missing", http.StatusBadRequest)
3449
return
3550
}
36-
ctx := r.Context()
51+
3752
if timeout := r.URL.Query().Get("timeout"); timeout != "" {
53+
var duration time.Duration
54+
var err error
3855
if regexpOnlyNumbers.MatchString(timeout) {
39-
seconds, err := strconv.Atoi(timeout)
40-
if err != nil {
41-
http.Error(w, fmt.Sprintf("invalid 'timeout' query parameter: %v", err), http.StatusBadRequest)
42-
return
43-
}
44-
timeoutCtx, cancelTimeout := context.WithTimeout(r.Context(), time.Duration(seconds)*time.Second)
45-
defer cancelTimeout()
46-
ctx = timeoutCtx
56+
var seconds int
57+
seconds, err = strconv.Atoi(timeout)
58+
duration = time.Second * time.Duration(seconds)
4759
} else {
48-
timeout, err := time.ParseDuration(timeout)
49-
if err != nil {
50-
http.Error(w, fmt.Sprintf("invalid 'timeout' query parameter: %v", err), http.StatusBadRequest)
51-
return
52-
}
53-
timeoutCtx, cancelTimeout := context.WithTimeout(r.Context(), timeout)
54-
defer cancelTimeout()
55-
ctx = timeoutCtx
60+
duration, err = time.ParseDuration(timeout)
5661
}
62+
63+
if err != nil {
64+
span.SetStatus(codes.Error, "invalid timeout query param")
65+
span.RecordError(err, trace.WithAttributes(attribute.String("timeout", timeout)))
66+
http.Error(w, fmt.Sprintf("invalid 'timeout' query parameter: %v", err), http.StatusBadRequest)
67+
return
68+
}
69+
timeoutCtx, cancelTimeout := context.WithTimeout(ctx, duration)
70+
defer cancelTimeout()
71+
ctx = timeoutCtx
72+
span.SetAttributes(attribute.String("timeout", duration.String()))
5773
}
5874
renderKey := r.URL.Query().Get("renderKey")
5975
domain := r.URL.Query().Get("domain")
6076
acceptLanguage := r.Header.Get("Accept-Language") // if empty, we just don't set it
77+
span.SetAttributes(
78+
attribute.String("acceptLanguage", acceptLanguage),
79+
attribute.String("renderKeyDomain", domain))
6180

6281
start := time.Now()
6382
contents, err := browser.RenderCSV(ctx, url, renderKey, domain, acceptLanguage)
6483
if err != nil {
6584
MetricRenderCSVDuration.WithLabelValues("error").Observe(time.Since(start).Seconds())
66-
http.Error(w, "CSV rendering failed", http.StatusInternalServerError)
67-
slog.ErrorContext(ctx, "failed to render CSV", "err", err)
85+
span.SetStatus(codes.Error, "csv rendering failed")
86+
span.RecordError(err)
87+
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
88+
http.Error(w, "request timed out", http.StatusRequestTimeout)
89+
} else if errors.Is(err, service.ErrInvalidBrowserOption) {
90+
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
91+
} else {
92+
http.Error(w, "CSV rendering failed", http.StatusInternalServerError)
93+
slog.ErrorContext(ctx, "failed to render CSV", "err", err)
94+
}
6895
return
6996
}
7097
MetricRenderCSVDuration.WithLabelValues("success").Observe(time.Since(start).Seconds())
98+
span.SetStatus(codes.Ok, "csv rendered successfully")
7199

72100
w.Header().Set("Content-Type", "text/csv")
73101
w.Header().Set("Content-Disposition", `attachment; filename="data.csv"`)

0 commit comments

Comments
 (0)