Zum Inhalt springen

So übertragen Sie Traces an Observability

Um Traces an Observability zu übertragen, müssen wir diese zunächst in einer Anwendung erzeugen.

Dazu sind die folgenden Schritte erforderlich:

  • Extrahieren des Span-Kontexts aus dem Request-Header.
  • Erstellen eines neuen Spans.
  • Abschließen des Spans.
  • Injizieren des neuen Spans.

Nachfolgend finden Sie Beispiele für das Golang-Framework Fiber sowie das Python-Framework Falcon:

Golang Fiber Tracing Middleware

package middlewares
import (
"observability-limit-api/internal/settings"
"fmt"
"github.com/opentracing/opentracing-go/ext"
"strings"
fiber "github.com/gofiber/fiber/v2"
opentracing "github.com/opentracing/opentracing-go"
_ "github.com/opentracing/opentracing-go/ext"
jaeger "github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
jaegerlog "github.com/uber/jaeger-client-go/log"
"github.com/uber/jaeger-lib/metrics/prometheus"
)
// init jaeger tracer.
var cfg = jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: fmt.Sprintf("%s:%s", settings.JaegerService, settings.JaegerPort),
},
}
var (
jLogger = jaegerlog.StdLogger
MetricsFactory = prometheus.New()
)
var _, _ = cfg.InitGlobalTracer(
"test-app",
jaegercfg.Logger(jLogger),
jaegercfg.Metrics(MetricsFactory),
)
var tracer = opentracing.GlobalTracer()
// TracingMiddleware to trace requests.
func TracingMiddleware(c *fiber.Ctx) error {
// build a header map from fiber req headers to extract jaeger header
headerMap:= getHeaderMap(c.GetReqHeaders())
// build carrier to build tracing chain
carrier:= opentracing.HTTPHeadersCarrier(headerMap)
spanCtx, _:= tracer.Extract(opentracing.HTTPHeaders, carrier)
// start span
span:= tracer.StartSpan("tracing middleware", ext.RPCServerOption(spanCtx))
err:= tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier)
if err!= nil {
return err
}
// get carrier header to set it in response headers
for key, el:= range carrier {
if strings.ToLower(key) == strings.ToLower(jaeger.TraceContextHeaderName) {
if len(el) > 0 {
c.Response().Header.Add(jaeger.TraceContextHeaderName, el[0])
c.Request().Header.Add(jaeger.TraceContextHeaderName, el[0])
}
}
}
// send request to route
err = c.Next()
if err!= nil {
return err
}
// append meta data to span
ext.HTTPMethod.Set(span, c.Method())
ext.HTTPUrl.Set(span, c.OriginalURL())
ext.HTTPStatusCode.Set(span, uint16(c.Response().StatusCode()))
// finish and inject span
span.Finish()
err = tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier)
if err!= nil {
return err
}
return nil
}
// getHeaderMap get http headers format.
func getHeaderMap(headers map[string]string) map[string][]string {
headerMap:= make(map[string][]string)
for key, element:= range headers {
if strings.EqualFold(strings.ToLower(key), strings.ToLower(jaeger.TraceContextHeaderName)) {
headerMap[jaeger.TraceContextHeaderName] = []string{element}
}
}
return headerMap
}
**Python Falcon Tracing Middleware**
```py
"""Tracing middleware."""
import os
import time
from typing import Dict
import falcon
import jaeger_client
from jaeger_client.constants import TRACE_ID_HEADER
from opentracing import Format, tags
class TracingMiddleware:
"""Tracing middleware."""
now = time.time()
tracer = jaeger_client.Config(
config={
"sampler": {"type": "const", "param": 1},
"local_agent": {
"reporting_port": int(
os.environ.get(
"JAEGER_AGENT_PORT", jaeger_client.config.DEFAULT_REPORTING_PORT
)
),
"reporting_host": os.environ.get("JAEGER_AGENT_HOST", "jaeger"),
},
"logging": bool(os.environ.get("JAEGER_LOGGING", True)),
},
service_name=os.environ.get("JAEGER_SERVICE_NAME", "test-app"),
validate=True,
).initialize_tracer()
def process_request(self, req: falcon.Request, resp: falcon.Response):
"""Request vor Eintritt in die Route verarbeiten."""
self.now = time.time()
def process_response(
self, req: falcon.Request, resp: falcon.Response, resource, req_succeeded
):
"""Response verarbeiten."""
span_ctx = self.tracer.extract(Format.HTTP_HEADERS, req.headers)
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
with self.tracer.start_span(
operation_name="test-app-response",
child_of=span_ctx,
tags=span_tags,
start_time=self.now,
) as gate_span:
self.inject_span(gate_span, req, resp)
def inject_span(self, span, request: falcon.Request, resp: falcon.Response):
"""Span injizieren."""
carrier: Dict[str, str] = {}
span.set_tag(tags.HTTP_METHOD, request.method)
span.set_tag(tags.HTTP_URL, request.path)
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
span.set_tag(tags.HTTP_STATUS_CODE, resp.status)
self.tracer.inject(span, Format.HTTP_HEADERS, carrier)
resp.set_header(TRACE_ID_HEADER, carrier[TRACE_ID_HEADER])

Als Nächstes benötigen wir einen Agenten im selben Netzwerk wie die Anwendung, da Traces von der Anwendung per UDP an den Agenten gesendet werden.
Eine gute Option ist der Opentelemetry Exporter, da er unterschiedlichste Trace-Typen empfangen und in verschiedene Formate exportieren kann.

Beispiel:

Opentelemetry Exporter Konfiguration

receivers:
jaeger:
protocols:
thrift_compact:
processors:
batch:
exporters:
otlp:
# To get the URL, make a GET request to the observability API with the path /v1/projects/{projectId}/instances/{instanceId}. The URL can be found in the "otlpTracesUrl" key of the "instance" dictionary in the response
   endpoint: <instance>-op.traces.testing.observability.eu01.stackit.cloud:443
    headers:
authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
service:
pipelines:
traces:
receivers: [jaeger]
processors: [batch]
exporters: [otlp]

Opentelemetry Config Zipkin

receivers:
jaeger:
protocols:
thrift_compact:
processors:
batch:
exporters:
zipkin:
endpoint: "https://<instance>-zk.traces.testing.observability.eu01.stackit.cloud/api/v2/spans"
headers:
authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
service:
pipelines:
traces:
receivers: [jaeger]
processors: [batch]
exporters: [zipkin]

Derzeit unterstützt Observability das Senden von Traces im Jaeger-, OTLP- oder Zipkin-Format. Die obigen Beispiele zeigen, wie Traces über OTLP mit gRPC und über Zipkin mit HTTP gesendet werden. Für eine bessere Performance empfehlen wir, Traces über gRPC zu senden.

Beispiel docker-compose:

docker-compose.yaml

version: "3"
services:
hotrod:
image: jaegertracing/example-hotrod:latest
ports:
- "8080:8080"
command: ["all"]
environment:
- JAEGER_AGENT_HOST=collector
- JAEGER_AGENT_PORT=6831
collector:
image: otel/opentelemetry-collector:0.23.0
command: "--config /etc/otel-config.yaml"
volumes:
-./otel-config.yaml:/etc/otel-config.yaml

In diesem Beispiel verwenden wir das Hotrod-Beispiel, um Beispiel-Traces zu erzeugen und an den Collector zu senden. Die Datei otel-config.yaml entspricht der YAML-Konfiguration aus den obigen Beispielen.

Nun können Sie in Grafana die Datenquelle „Tempo“ oder „Jaeger“ auswählen.
Bei der Jaeger-Datenquelle ist es wichtig, ein Limit von 20 zu setzen. Observability verwendet Tempo als Trace-Collector, und dieses Tool verfügt über keine Indexierung von Traces. Dadurch ist es schwierig, Traces gezielt zu finden.

Der empfohlene Weg zur Überprüfung von Traces ist die Verwendung der Datenquelle „Tempo“ sowie das Protokollieren der Traces. Diese Logs müssen an Loki von Observability gesendet werden.