Skip to content
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

feat: refactor network logs instrumentation #260

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ioannisj
Copy link
Contributor

@ioannisj ioannisj commented Nov 21, 2024

💡 Motivation and Context

Closes #204
Also some work on #159

This is an initial implementation of using a custom URLProtocol to intercept network traffic.

TODO:

  • Work on supporting custom URLSession and URLSessionConfiguration
  • PostHogHTTPProtocol currently uses it's own instance for URLSession to carry out the network. This should not be the case
  • Need to test how this behaves with various HTTP verbs (POST, PUT etc)
  • Either introduce captureBody, captureHeaders in config or remove body and header capture from this PR and address these separately

💚 How did you test it?

Sample project with async/await and traditional URLSession

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

Copy link

Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Next" section. Make sure the entry includes this PR's number.
Example:

## Next
- refactor network logs instrumentation ([#260](https://github.com/PostHog/posthog-ios/pull/260))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description.

Generated by 🚫 dangerJS against dc25e44

@@ -12,3 +12,10 @@ func swizzle(forClass: AnyClass, original: Selector, new: Selector) {
guard let swizzledMethod = class_getInstanceMethod(forClass, new) else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}

func swizzleClassMethod(forClass: AnyClass, original: Selector, new: Selector) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename the one above to make more clear its about instance method?

Comment on lines +84 to +86
// don't record response body for image types
let bodyStr = requestData.base64EncodedString(options: .endLineWithLineFeed)
requestBodyLength = bodyStr.count
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

body length in bytes is != than base64 encoded char count.
we can probably read Content-Length here or just count bytes from requestData

}

// grab request body
if let requestData = request.httpBody ?? request.httpBodyStream?.consume() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this has a performance impact + PII risk, we should add a new flag captureBody as part of session replay config

Comment on lines +115 to +124
if let responseData = responseData as? Data {
if let responseContentType, responseContentType == .image {
// don't record response body for image types
let bodyStr = responseData.base64EncodedString(options: .endLineWithLineFeed)
responseBodyLength = bodyStr.count
} else if let utfString = String(data: responseData, encoding: String.Encoding.utf8) {
responseBodyStr = utfString
responseBodyLength = utfString.count
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if 100% the same but we can extract from start and reuse it here? if responseData and requestData are of the same type

}
}

private func getMonotonicTimeInMilliseconds() -> UInt64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMonotonicTimeInMilliseconds already exists, can we reuse it? See URLSession extension

requestStartTime = getMonotonicTimeInMilliseconds()
requestURL = request.url?.absoluteURL
requestMethod = request.httpMethod
requestHeaders = request.normalizedHeaderFields ?? [:]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


if session == nil {
// ⚠️ - This is currently always using a fresh session with the `default` configuration
// - Need to figure out a way to use custom sessions and configurations here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's one of the reasons I gave up on URLProtocol back then since it'd require figuring out more stuff rather than inspiring from the current DD approach.
We need to be sure that React native works as well, I'd need to check if they use a custom or not.

}

private static func canHandle(task: URLSessionTask) -> Bool {
if #available(iOS 13.0, macOS 10.15, *) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we dont need this I think
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6),
so the SDK is always running on >= than that

let bodyStr = responseData.base64EncodedString(options: .endLineWithLineFeed)
responseBodyLength = bodyStr.count
} else if let utfString = String(data: responseData, encoding: String.Encoding.utf8) {
responseBodyStr = utfString
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


snapshotsData.append(data)

PostHogSDK.shared.capture("$snapshot", properties: [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to check isCaptureNetworkEnabled before processing a request and before capturing so we are sure we don't capture when there's no active session

@marandaneto
Copy link
Member

@ioannisj left a few comments, no need to address all of them, more about sharing some context around a few items.
looking good.

One suggestion is to not capture headers and body for now, so we can test/ship this PR faster.

This comment is important to figure out.

Make sure we fix the side effect of this issue.

@ioannisj
Copy link
Contributor Author

@ioannisj left a few comments, no need to address all of them, more about sharing some context around a few items. looking good.

One suggestion is to not capture headers and body for now, so we can test/ship this PR faster.

This comment is important to figure out.

Make sure we fix the side effect of this issue.

Yeah, that could potentially be a blocker for this approach actually. Looking into it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refactor network logs instrumentation for session replay
2 participants