-
Notifications
You must be signed in to change notification settings - Fork 55
Add Dio interceptor for capturing network events with Posthog #220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a Dio interceptor to enable automatic capturing of network events for session replay and network performance monitoring with PostHog. The interceptor integrates with Dio's request/response lifecycle to track HTTP requests and responses.
- Adds
PosthogDioInterceptorclass that extends Dio'sInterceptor - Captures network events on both successful responses and errors
- Transforms request/response data into PostHog-compatible snapshot events
- Adds Dio as a direct dependency
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
pubspec.yaml |
Adds Dio 5.9.0 as a new dependency to support the network interceptor |
lib/src/replay/network_performance/dio_interceptor.dart |
Implements the PosthogDioInterceptor class that captures network events from Dio requests/responses and sends them to PostHog as snapshot events |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
pubspec.yaml
Outdated
| # plugin_platform_interface depends on meta anyway | ||
| meta: ^1.3.0 | ||
| stack_trace: ^1.12.0 | ||
| dio: ^5.9.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this has to be a new package within the posthog-flutter repo so users that dont use dio dont need to have this as a dependency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
| import 'package:dio/dio.dart'; | ||
| import 'package:posthog_flutter/posthog_flutter.dart'; | ||
|
|
||
| class PosthogDioInterceptor extends Interceptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| class PosthogDioInterceptor extends Interceptor { | |
| class PostHogDioInterceptor extends Interceptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
| if (publishableRequest != null) 'request': publishableRequest, | ||
| if (publishableResponse != null) 'response': publishableResponse, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we should skip this for now, or we should check its body size and type before appending to the properties.
events that are large will be dropped, and it has to be json serializable, so better to skip for a 1st version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Size concern: May I know what's the size limit here? If you let me know, I can drop it.
- json serializable ensurance: actually
_tryTransformDataToPublishableObjecthas already transformed them to publishable object that is ensured to be json serializable
Kinda need this for my usage, so if possible I plan to have it in the 1st version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda need this for my usage, so if possible I plan to have it in the 1st version.
Expose a ctor param when creating a PosthogDioInterceptor instance, so users can optin/out of attaching payloads.
like attachBodies or attachPayloads with default false due to PII risk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Size concern: May I know what's the size limit here? If you let me know, I can drop it.
lets dow hat the JS SDK does
https://github.com/PostHog/posthog-js/blob/962649dcc87ff67da2e80e7dfb776020b903bc7b/packages/browser/src/extensions/replay/external/config.ts#L130-L170
you can inspire from this implementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
| }, | ||
| 'timestamp': DateTime.now().millisecondsSinceEpoch, | ||
| }; | ||
| await Posthog().capture( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you dont need to await this, so you can remove the Future<void> from this method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still need it after the latest change.
| Response<dynamic> response, | ||
| ResponseInterceptorHandler handler, | ||
| ) async { | ||
| await _captureNetworkEvent( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should only call this method if (check ScreenshotCapturer):
final isSessionReplayActive =
await _nativeCommunicator.isSessionReplayActive();
so we dont try to capture snapshot network requests if replay is deactivated, this method is private, we'd need to expose internally at least
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
they are not analytics events, they are session replay metata, it wont appear in the Activity page, they will be part of a recording in the session replay recordings |
| ) async { | ||
| final Response<dynamic>? response = err.response; | ||
| if (response != null) { | ||
| await _captureNetworkEvent(response: response); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are checking response here, but I think this will miss network connection errors, connection timeouts etc which may not have a response object returned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, isn't this supposed to be captured by the Error Tracking automatically (unless user catch the error)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes but not with the same level of details as you have here (you have more context in this method).
what you could do is: try catch, read what you need and rethrow the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really think we should rethrow the error here, the interceptor is supposed to just send whatever we want here.
If we throw here, the next interceptor that user configures will not be called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if _captureNetworkEvent throws, we wont catch/log anything.
if we try catch and rethrow, this is the behaviour that would happen anyway.
same approach i've done before https://github.com/getsentry/sentry-dart/blob/20faa47cb637900f4a6112a89bd71419fd7bc16d/packages/dio/lib/src/tracing_client_adapter.dart#L68-L80
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused about the comment after a few replies. Let me try and restructure first.
So, initially the ask is to handle the errors from the overriding method: onError, in which it contains network connection errors, connection timeouts etc. I didn't want to handle it because I feel that this is supposed to be handled by the Error Tracking.
Then I was asked to catch the error here:
yes but not with the same level of details as you have here (you have more context in this method).
what you could do is: try catch, read what you need and rethrow the error.
which is weird because technically this is different ask compared to the original handling of the DioException from the onError method.
If we throw here, the next interceptor that user configures will not be called.
This will no longer be true after my latest changes of moving the call of _captureNetworkEvent after super.onError.
All in all, I don't mind handling the err from onError but I need a sample of implementing it, do I just send the stack trace along or just the error message? If we want stack trace then I still think it's better to use Posthog.captureException as this is the easiest way for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a misunderstanding here.
We're not talking about onError
We're talking about being able to create a network request snapshot even if there's an error.
Not talking about network errors, but rather API errors eg 4xx or 5xx, you still want network request snapshots.
If dio throws an exception in this use case, we have to try-catch-rethrow so you are able to do so.
|
|
||
| # Add regular dependencies here. | ||
| dependencies: | ||
| dio: ^5.9.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| dio: ^5.9.0 | |
| dio: ^5.0.0 |
if we dont use anything new, we can target the major instead of the latest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
| # Add regular dependencies here. | ||
| dependencies: | ||
| dio: ^5.9.0 | ||
| posthog_flutter: ^5.9.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| posthog_flutter: ^5.9.0 | |
| posthog_flutter: ^5.0.0 |
same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
| final hasExceededLimit = await _hasExceededSizeLimit( | ||
| data: response.requestOptions.data, | ||
| header: response.requestOptions.headers, | ||
| ); | ||
| return ( | ||
| value, | ||
| hasExceededLimit, | ||
| ); | ||
| }, | ||
| ), | ||
| if (attachPayloads) | ||
| _tryTransformDataToPublishableObject( | ||
| data: response.data, | ||
| ).then( | ||
| (value) async { | ||
| final hasExceededLimit = await _hasExceededSizeLimit( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have to sum the request payload + response payload to tell if hasExceededLimit or not since what matters is the size of the final posthog event built here snapshotData
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
|
|
||
| /// A Dio interceptor that captures network events and sends them to PostHog. | ||
| class PostHogDioInterceptor extends Interceptor { | ||
| final NativeCommunicator _nativeCommunicator = NativeCommunicator(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of creating a new instance for this, we should expose and reuse this class
| final _nativeCommunicator = NativeCommunicator(); |
example: https://github.com/PostHog/posthog-android/blob/743a341365f5d9c1cf254a7b01882b59c3089e30/posthog/src/main/java/com/posthog/PostHog.kt#L1096-L1102
we expose a public method in the posthog class so we can call it directly from here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
|
now i think our example should depend on the new package and add a button in the sample for testing |
Done moving to package and adding button for testing. But can you help with the workflow? It seems that it requires approval, not sure if this is a one-time thing or the approval has to be granted every time, if it's latter, I think it's better that the maintainer handles this. |
|
@TabooSun I'm handling an incident now, but will follow up on comments/maybe open a PR on top of yours with suggestions later on (later this week or early next week), so we avoid the back and forth since I am focusing on other tasks that have higher priority for now, sorry about that. |
|
@marandaneto ping on this one |
|
@TabooSun mind fixing conflicts after your changes? so i can do a final review/release if all good? |
…ialization to separate isolates
…lculateSizeLimit` and improve payload size validation logic
…unicator` with `Posthog` in Dio interceptor
…`_callApiWithDio` example to showcase usage
167a0d9 to
9616f39
Compare
ad638e3 to
18cb2f7
Compare
Hi, updated~ |

💡 Motivation and Context
Close #169
Didn't manage to test the code because when I check the Posthog dashboard, none of the events are recorded in the
Activitypane, not sure if it is filtered, might need help here.💚 How did you test it?
📝 Checklist