Skip to content

Commit a457141

Browse files
szuecsAlexanderYastrebov
authored andcommitted
Feature: tracing set route id to ingress spans (zalando#2714)
* feature: set Tag "skipper.route_id" to `route.Id` in all ingress spans including shunt and loopback routes Signed-off-by: Sandor Szücs <[email protected]>
1 parent 799cf97 commit a457141

File tree

3 files changed

+162
-5
lines changed

3 files changed

+162
-5
lines changed

proxy/context.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ func (c *context) Split() (filters.FilterContext, error) {
296296
}
297297

298298
func (c *context) Loopback() {
299-
err := c.proxy.do(c)
299+
loopSpan := c.Tracer().StartSpan(c.proxy.tracing.initialOperationName, opentracing.ChildOf(c.ParentSpan().Context()))
300+
defer loopSpan.Finish()
301+
err := c.proxy.do(c, loopSpan)
300302
if c.response != nil && c.response.Body != nil {
301303
if _, err := io.Copy(io.Discard, c.response.Body); err != nil {
302304
c.Logger().Errorf("context: error while discarding remainder response body: %v.", err)

proxy/proxy.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ func stack() []byte {
10241024
}
10251025
}
10261026

1027-
func (p *Proxy) do(ctx *context) (err error) {
1027+
func (p *Proxy) do(ctx *context, parentSpan ot.Span) (err error) {
10281028
defer func() {
10291029
if r := recover(); r != nil {
10301030
p.onPanicSometimes.Do(func() {
@@ -1068,6 +1068,7 @@ func (p *Proxy) do(ctx *context) (err error) {
10681068
p.makeErrorResponse(ctx, errRouteLookupFailed)
10691069
return errRouteLookupFailed
10701070
}
1071+
parentSpan.SetTag(SkipperRouteIDTag, route.Id)
10711072

10721073
ctx.applyRoute(route, params, p.flags.PreserveHost())
10731074

@@ -1086,7 +1087,16 @@ func (p *Proxy) do(ctx *context) (err error) {
10861087
ctx.ensureDefaultResponse()
10871088
} else if ctx.route.BackendType == eskip.LoopBackend {
10881089
loopCTX := ctx.clone()
1089-
if err := p.do(loopCTX); err != nil {
1090+
loopSpan := tracing.CreateSpan("loopback", ctx.request.Context(), p.tracing.tracer)
1091+
p.tracing.
1092+
setTag(loopSpan, SpanKindTag, SpanKindServer).
1093+
setTag(loopSpan, SkipperRouteIDTag, ctx.route.Id)
1094+
p.setCommonSpanInfo(ctx.Request().URL, ctx.Request(), loopSpan)
1095+
ctx.parentSpan = loopSpan
1096+
1097+
defer loopSpan.Finish()
1098+
1099+
if err := p.do(loopCTX, loopSpan); err != nil {
10901100
// in case of error we have to copy the response in this recursion unwinding
10911101
ctx.response = loopCTX.response
10921102
if err != nil {
@@ -1442,6 +1452,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
14421452
ctx.startServe = time.Now()
14431453
ctx.tracer = p.tracing.tracer
14441454
ctx.initialSpan = span
1455+
ctx.parentSpan = span
14451456

14461457
defer func() {
14471458
if ctx.response != nil && ctx.response.Body != nil {
@@ -1452,7 +1463,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
14521463
}
14531464
}()
14541465

1455-
err := p.do(ctx)
1466+
err := p.do(ctx, span)
14561467

14571468
// writeTimeout() filter
14581469
if d, ok := ctx.StateBag()[filters.WriteTimeout].(time.Duration); ok {

proxy/tracing_test.go

+145-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ func TestTracingIngressSpan(t *testing.T) {
8484
})
8585
defer s.Close()
8686

87-
doc := fmt.Sprintf(`hello: Path("/hello") -> setPath("/bye") -> setQuery("void") -> "%s"`, s.URL)
87+
routeID := "ingressRoute"
88+
doc := fmt.Sprintf(`%s: Path("/hello") -> setPath("/bye") -> setQuery("void") -> "%s"`, routeID, s.URL)
8889

8990
tracer := mocktracer.New()
9091
params := Params{
@@ -126,6 +127,7 @@ func TestTracingIngressSpan(t *testing.T) {
126127

127128
verifyTag(t, span, SpanKindTag, SpanKindServer)
128129
verifyTag(t, span, ComponentTag, "skipper")
130+
verifyTag(t, span, SkipperRouteIDTag, routeID)
129131
// to save memory we dropped the URL tag from ingress span
130132
//verifyTag(t, span, HTTPUrlTag, "/hello?world") // For server requests there is no scheme://host:port, see https://golang.org/pkg/net/http/#Request
131133
verifyTag(t, span, HTTPMethodTag, "GET")
@@ -137,6 +139,139 @@ func TestTracingIngressSpan(t *testing.T) {
137139
verifyHasTag(t, span, HTTPRemoteIPTag)
138140
}
139141

142+
func TestTracingIngressSpanShunt(t *testing.T) {
143+
routeID := "ingressShuntRoute"
144+
doc := fmt.Sprintf(`%s: Path("/hello") -> setPath("/bye") -> setQuery("void") -> status(205) -> <shunt>`, routeID)
145+
146+
tracer := mocktracer.New()
147+
params := Params{
148+
OpenTracing: &OpenTracingParams{
149+
Tracer: tracer,
150+
},
151+
Flags: FlagsNone,
152+
}
153+
154+
t.Setenv("HOSTNAME", "ingress-shunt.tracing.test")
155+
156+
tp, err := newTestProxyWithParams(doc, params)
157+
if err != nil {
158+
t.Fatal(err)
159+
}
160+
defer tp.close()
161+
162+
ps := httptest.NewServer(tp.proxy)
163+
defer ps.Close()
164+
165+
req, err := http.NewRequest("GET", ps.URL+"/hello?world", nil)
166+
if err != nil {
167+
t.Fatal(err)
168+
}
169+
req.Header.Set("X-Flow-Id", "test-flow-id")
170+
171+
rsp, err := ps.Client().Do(req)
172+
if err != nil {
173+
t.Fatal(err)
174+
}
175+
defer rsp.Body.Close()
176+
io.Copy(io.Discard, rsp.Body)
177+
178+
// client may get response before proxy finishes span
179+
time.Sleep(10 * time.Millisecond)
180+
181+
span, ok := findSpan(tracer, "ingress")
182+
if !ok {
183+
t.Fatal("ingress span not found")
184+
}
185+
186+
verifyTag(t, span, SpanKindTag, SpanKindServer)
187+
verifyTag(t, span, ComponentTag, "skipper")
188+
verifyTag(t, span, SkipperRouteIDTag, routeID)
189+
// to save memory we dropped the URL tag from ingress span
190+
//verifyTag(t, span, HTTPUrlTag, "/hello?world") // For server requests there is no scheme://host:port, see https://golang.org/pkg/net/http/#Request
191+
verifyTag(t, span, HTTPMethodTag, "GET")
192+
verifyTag(t, span, HostnameTag, "ingress-shunt.tracing.test")
193+
verifyTag(t, span, HTTPPathTag, "/hello")
194+
verifyTag(t, span, HTTPHostTag, ps.Listener.Addr().String())
195+
verifyTag(t, span, FlowIDTag, "test-flow-id")
196+
verifyTag(t, span, HTTPStatusCodeTag, uint16(205))
197+
verifyHasTag(t, span, HTTPRemoteIPTag)
198+
}
199+
200+
func TestTracingIngressSpanLoopback(t *testing.T) {
201+
shuntRouteID := "ingressShuntRoute"
202+
loop1RouteID := "loop1Route"
203+
loop2RouteID := "loop2Route"
204+
routeIDs := []string{loop2RouteID, loop1RouteID, shuntRouteID}
205+
paths := map[string]string{
206+
loop2RouteID: "/loop2",
207+
loop1RouteID: "/loop1",
208+
shuntRouteID: "/shunt",
209+
}
210+
211+
doc := fmt.Sprintf(`
212+
%s: Path("/shunt") -> setPath("/bye") -> setQuery("void") -> status(204) -> <shunt>;
213+
%s: Path("/loop1") -> setPath("/shunt") -> <loopback>;
214+
%s: Path("/loop2") -> setPath("/loop1") -> <loopback>;
215+
`, shuntRouteID, loop1RouteID, loop2RouteID)
216+
217+
tracer := mocktracer.New()
218+
params := Params{
219+
OpenTracing: &OpenTracingParams{
220+
Tracer: tracer,
221+
},
222+
Flags: FlagsNone,
223+
}
224+
225+
t.Setenv("HOSTNAME", "ingress-loop.tracing.test")
226+
227+
tp, err := newTestProxyWithParams(doc, params)
228+
if err != nil {
229+
t.Fatal(err)
230+
}
231+
defer tp.close()
232+
233+
ps := httptest.NewServer(tp.proxy)
234+
defer ps.Close()
235+
236+
req, err := http.NewRequest("GET", ps.URL+"/loop2", nil)
237+
if err != nil {
238+
t.Fatal(err)
239+
}
240+
req.Header.Set("X-Flow-Id", "test-flow-id")
241+
242+
rsp, err := ps.Client().Do(req)
243+
if err != nil {
244+
t.Fatal(err)
245+
}
246+
defer rsp.Body.Close()
247+
io.Copy(io.Discard, rsp.Body)
248+
t.Logf("got response %d", rsp.StatusCode)
249+
250+
// client may get response before proxy finishes span
251+
time.Sleep(10 * time.Millisecond)
252+
253+
sp, ok := findSpanByRouteID(tracer, loop2RouteID)
254+
if !ok {
255+
t.Fatalf("span for route %q not found", loop2RouteID)
256+
}
257+
verifyTag(t, sp, HTTPStatusCodeTag, uint16(204))
258+
259+
for _, rid := range routeIDs {
260+
span, ok := findSpanByRouteID(tracer, rid)
261+
if !ok {
262+
t.Fatalf("span for route %q not found", rid)
263+
}
264+
verifyTag(t, span, SpanKindTag, SpanKindServer)
265+
verifyTag(t, span, ComponentTag, "skipper")
266+
verifyTag(t, span, SkipperRouteIDTag, rid)
267+
verifyTag(t, span, HTTPMethodTag, "GET")
268+
verifyTag(t, span, HostnameTag, "ingress-loop.tracing.test")
269+
verifyTag(t, span, HTTPPathTag, paths[rid])
270+
verifyTag(t, span, HTTPHostTag, ps.Listener.Addr().String())
271+
verifyTag(t, span, FlowIDTag, "test-flow-id")
272+
}
273+
}
274+
140275
func TestTracingSpanName(t *testing.T) {
141276
traceContent := fmt.Sprintf("%x", md5.New().Sum([]byte(time.Now().String())))
142277
s := startTestServer(nil, 0, func(r *http.Request) {
@@ -562,6 +697,15 @@ func findSpan(tracer *mocktracer.MockTracer, name string) (*mocktracer.MockSpan,
562697
return nil, false
563698
}
564699

700+
func findSpanByRouteID(tracer *mocktracer.MockTracer, routeID string) (*mocktracer.MockSpan, bool) {
701+
for _, s := range tracer.FinishedSpans() {
702+
if s.Tag(SkipperRouteIDTag) == routeID {
703+
return s, true
704+
}
705+
}
706+
return nil, false
707+
}
708+
565709
func verifyTag(t *testing.T, span *mocktracer.MockSpan, name string, expected interface{}) {
566710
t.Helper()
567711
if got := span.Tag(name); got != expected {

0 commit comments

Comments
 (0)