DataDog / dd-sdk-flutter

Flutter bindings and tools for utilizing Datadog Mobile SDKs
Apache License 2.0
45 stars 41 forks source link

Customize RUM HttpTracking #397

Closed ClaireDavis closed 9 months ago

ClaireDavis commented 1 year ago

We are using RUM in our flutter application and have enabled http tracking using the enableHttpTracking method on the DdSdkConfiguration. The problem is that our application also uses GraphQL and the automatic http tracking in the datadog framework records the resource.url_path which is the same for every GraphQL operation. This makes the logs pretty meaningless. I'd like to be able to include the operation name in logs but there doesn't appear to be a way to do so.

I tried to use the rumResourceEventMapper, but when this is called, the event has no context on what the operation was.

Do you have any suggestions on how to achieve this within the current framework?

current plugins + versions: datadog_flutter_plugin: ^1.3.2 datadog_tracking_http_client: ^1.2.1

fuzzybinary commented 1 year ago

Hi @ClaireDavis ,

Unfortunately there's nothing you can do currently other than potentially tracking the resources manually with DdRum.startResource / DdRum.stopResource and adding attributes to those calls.

However, we're currently looking into ways to support GraphQL more directly. I'll update the issue when we have more information available.

ClaireDavis commented 1 year ago

@fuzzybinary thanks for the prompt reply!

Any additional GraphQL support would be greatly appreciated - thanks for keeping us updated! We'll look into using startResource/stopResource in the meantime

fuzzybinary commented 1 year ago

HI @ClaireDavis,

A few quick questions for you:

First, what package(s) are you using for GraphQL support in Flutter? Are you using graphql?

Second, we're hooking into the network libraries at a fairly low level, which makes it hard to return data to any listening callbacks. Would getting the HttpClientRequest / HttpClientResponse be enough to get the data you need, or would you need the body of the requests and responses as well?

ClaireDavis commented 1 year ago

Hi @fuzzybinary - we are using Ferry, not graphql, but from my research, they operate in very similar ways and share a lot of the same underlying packages.

Our intermediate solution was to use an Interceptor and append the query name to the end of the url. As long as we have access to the Query name, we should be fine, but I believe that is part of the data of the request

fuzzybinary commented 1 year ago

Yes, usually the query is part of the body. I think there's something workable in the listener that will be part of datadog_tracking_http_client 1.3.0, but it's not as easy as I'd like. I'll take a look at Ferry and see if I can make something workable.

We're still investigating more robust graphql support, but hopefully this will help you in the short term.

ClaireDavis commented 1 year ago

Awesome! I'll check out what's available in 1.3.0. I appreciate the support!

fuzzybinary commented 1 year ago

@ClaireDavis 1.3.0 is now available. Please keep me posted if you have any issues and whether the provided API works for you.

ClaireDavis commented 1 year ago

Hey @fuzzybinary - I was looking to upgrade to 1.3.0 and use the DatadogTrackingHttpClientListener but it appears to not be exported as part of datadog_tracking_http_client.dart. I put up a pr to add that. Feel free to close and diy it if that's not sufficient though!

ClaireDavis commented 1 year ago

I spent some time playing around with 1.3.0 and, as expected, there's not a great way to get access to the Query name from within the DatadogTrackingHttpClientListener since it is part of the request body. I was trying to think of some things that might make it simpler given the limitations. From what I can tell, there isn't a way to read the data off of the HttpClientResponse since the stream has already been listened to when the responseFinished function is called. We also can't read it off of HttpClientRequest. So in order to be able to add a query_name to the userAttributes map, we need some way to associate the request or response with a request that was sent by our client or to be able to expose the body of the request to the listener. I do see what you're saying about hooking into the network requests at a very low level. It's a tricky problem! I'm interested if you have any ideas :) perhaps there's something I'm missing 🤔

fuzzybinary commented 1 year ago

No there's nothing you're missing... I'm going to have to think about it.

I may look into providing a custom gql_link, but I'll have to determine how to have that interoperate with the tracking http client properly.

Kiruel commented 1 year ago

Hi ! Maybe I can add some ressources. I'm using graphql too on my project this is my setting for it:

@Riverpod(keepAlive: true)
GraphQLClient graphQlClient(GraphQlClientRef ref) => GraphQLClient(
      cache: GraphQLCache(
        store: kIsWeb
            ? InMemoryStore()
            : HiveStore(
                Hive.box<Map<dynamic, dynamic>?>(hiveBoxVersion),
              ),
      ),
      defaultPolicies: DefaultPolicies(
        mutate: Policies(fetch: FetchPolicy.networkOnly),
        query: Policies(fetch: FetchPolicy.networkOnly),
        subscribe: Policies(fetch: FetchPolicy.networkOnly),
        watchMutation: Policies(fetch: FetchPolicy.networkOnly),
        watchQuery: Policies(fetch: FetchPolicy.networkOnly),
      ),
      link: Link.from([
        ErrorLink(
          onGraphQLError: (request, forward, response) {
            if (response.errors?.first.extensions?['code'] == 'UNAUTHORIZED') {
              // Here capture errors from graphql
            }
            return;
          },
        ),
        DioLink(
          'GRAPHQL_API_URL',
          client: Dio(
            BaseOptions(
              baseUrl: 'BASE_URL',
              connectTimeout: const Duration(milliseconds: 5000),
              receiveTimeout: const Duration(milliseconds: 20000),
              contentType: Headers.jsonContentType,
            ),
          )
            ..interceptors.add(
              OauthInterceptor(ref: ref.container),
            )
            ..interceptors.addAll([
              // if (!kReleaseMode)
              //   LogInterceptor(
              //     responseBody: true,
              //     requestBody: true,
              //   ),
            ]),
        ),
      ]),
    );

Like we can see I use DioLink very helpful here because I could use Dio client on Graphql to setup interceptor for example. Maybe we could have an interceptor for datadog and pass the config to it ?

Kiruel commented 1 year ago

Looking for something like this https://github.com/GetDutchie/datadog_flutter/issues/47#issuecomment-915049269

fuzzybinary commented 1 year ago

Since we already automatically track Dio requests at a lower level which that's why we haven't supplied a standalone interceptor similar to the one provided for the Dutchie implementation. , The problem is that it's difficult to add custom attributes to, especially for Graph QL. We've been delaying GraphQL support to try to get something more fully featured, but I will look into providing an early gql_link implementation that's a bit more customizable.

I'll keep you all posted!

fuzzybinary commented 1 year ago

Hi folks!

The datadog_gql_link can be tested if you'd like, with a few caveats. Right now, it is incompatible with datadog_tracking_http_client because both will attempt to start and stop resources (so you'll end up with double resources). Because of this, and because we're adding additional functionality into the core Native APIs specifically for GraphQL, I'm going to hold off on deploying this to pub until that's all in place.

If you would like to try the new link though, you can add it as a git dependency and let me know if you have any feedback.

fuzzybinary commented 9 months ago

datadog_gql_link is now live as an official package. I think that's the main request of this ticket so I'm going to close it for now. Let me know if you have any issues with the new package!

ClaireDavis commented 9 months ago

thank you @fuzzybinary!