toMiddleware method

  1. @override
Middleware toMiddleware()
override

Builds the shelf middleware this value configures.

Implementation

@override
Middleware toMiddleware() {
  return (Handler inner) {
    return (Request request) async {
      final parts = RequestParts.of(request);
      final parent = TraceContext.parse(parts.headers['traceparent']);

      final span = Span(
        name: '${parts.method} ${request.requestedUri.path}',
        context: parent?.child() ?? TraceContext.start(),
        parentSpanId: parent?.spanId,
        attributes: {
          'http.request.method': parts.method,
          'url.path': request.requestedUri.path,
          if (request.requestedUri.query.isNotEmpty)
            'url.query': request.requestedUri.query,
          if (serviceName != null) 'service.name': serviceName,
        },
      );

      // The router fills this on the way through; a layer wraps the matcher,
      // so it cannot read the route off its own request.
      final matched = MatchedRouteSlot();
      final traced = request.change(
        context: {matchedRouteSlotKey: matched},
      );

      try {
        final response = await CurrentSpan.runWith(
          span,
          () => Future.sync(() => inner(traced)),
        );

        // The route is only known once the matcher has run, so the span is
        // renamed here rather than guessed up front. An explicit `nameSpan`
        // wins: a handler that said what it is doing knows better than the
        // route table, which is the case for a mounted service.
        if (matched.route case final route?
            when !span.attributes.containsKey('http.route')) {
          span
            ..name = '${parts.method} $route'
            ..setAttribute('http.route', route);
        }

        span
          ..setAttribute('http.response.status_code', response.statusCode)
          // A 4xx is the server working correctly, so only 5xx is an error;
          // marking 404 as failure buries the failures that matter.
          ..end(
              status: response.statusCode >= 500
                  ? SpanStatus.error
                  : SpanStatus.ok);

        _export(span);
        return response.change(
          headers: {'traceparent': span.context.traceparent},
        );
      } on HijackException {
        // Taking over the socket is how a WebSocket upgrade *succeeds*, and
        // `shelf` signals it by throwing. Treated as an error, every working
        // WebSocket endpoint reads as a total failure in a trace backend —
        // the connections that succeeded are exactly the ones marked broken.
        span
          ..setAttribute('http.response.status_code', 101)
          ..end(status: SpanStatus.ok);
        _export(span);
        rethrow;
      } on Object catch (error) {
        span
          ..setAttribute('error.type', error.runtimeType.toString())
          ..end(status: SpanStatus.error);
        _export(span);
        rethrow;
      }
    };
  };
}