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

On by default session tracking #286

Merged
merged 30 commits into from
Jun 21, 2018
Merged

Conversation

fractalwrench
Copy link
Contributor

@fractalwrench fractalwrench commented Jun 6, 2018

Goal

Enables automatic tracking of sessions by default, and adds a new API for setting the notify/session endpoints.

Design

This should prevent the unintentional scenario where a user sets the notify endpoint with a sessions endpoint.

Changeset

Added

setEndpointsForNotify:sessions:

Removed

Changed

  • Updated docs for sessionURL and notifyURL to indicate they should be only used to read the value, not write.
  • Auto capture flag is now true by default

Discussion

Outstanding Questions

  • What should our approach be to deprecating the notifyURL and sessionURL properties? We may want to keep these properties to allow users to retrieve the value they have set. One option would be to deprecate them entirely, another would be to change to readonly rather than readwrite.

  • What is our approach for writing mazerunner scenarios? We're missing scenarios for the session tracking feature entirely, so presumably we need to write these in addition to the new test cases in the design spec. Cocoa mazerunner scenarios also don't currently work on my machine, and I'm concerned that waiting 20-30 minutes for CI to run the scenarios is a very long feedback loop.

Review

For the submitter, initial self-review:

  • Commented on code changes inline explain the reasoning behind the approach
  • Reviewed the test cases added for completeness and possible points for discussion
  • A changelog entry was added for the goal of this pull request
  • Check the scope of the changeset - is everything in the diff required for the pull request?
  • This pull request is ready for:
    • Initial review of the intended approach, not yet feature complete
    • Structural review of the classes, functions, and properties modified
    • Final review

For the pull request reviewer(s), this changeset has been reviewed for:

  • Consistency across platforms for structures or concepts added or modified
  • Consistency between the changeset and the goal stated above
  • Internal consistency with the rest of the library - is there any overlap between existing interfaces and any which have been added?
  • Usage friction - is the proposed change in usage cumbersome or complicated?
  • Performance and complexity - are there any cases of unexpected O(n^3) when iterating, recursing, flat mapping, etc?
  • Concurrency concerns - if components are accessed asynchronously, what issues will arise
  • Thoroughness of added tests and any missing edge cases
  • Idiomatic use of the language

@fractalwrench fractalwrench requested review from kattrali and a team June 6, 2018 09:15
@fractalwrench
Copy link
Contributor Author

I've now added some mazerunner scenarios which should add coverage for session tracking. I believe I've found a couple of issues, the first being that the session request sends user.emailAddress rather than user.email. I've corrected this in e12ff70.

The second issue is that the AutoSessionScenario scenario appears to receive 2 requests, rather than 1. I'm not 100% sure why this occurs, but it's possible that (willEnterForeground)[https://github.com/bugsnag/bugsnag-cocoa/blob/master/Source/BugsnagNotifier.m#L360] is called multiple times, resulting in a duplicate number of sessions. I've created a separate ticket to investigate this further.

Copy link

@martin308 martin308 left a comment

Choose a reason for hiding this comment

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

Added some comments on some things that might be good to look into

* but should be overridden if you are using Bugsnag On-premise, to point to your own Bugsnag endpoint.
* Retrieves the endpoint used to notify Bugsnag of errors
*
* NOTE: it is strongly recommended that you set this value via setEndpointsForNotify:sessions: instead.

Choose a reason for hiding this comment

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

is it worth using something like this to make this as deprecated?

Copy link
Contributor Author

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 outstanding questions - what are your thoughts on this? If we add setEndpointsForNotify:sessions:, then developers are able to set the value, but won't be able to read it without notifyURL and sessionsURL.

The way I see it there are a couple of options:

  • Leave notifyURL and sessionsURL as is and document they shouldn't be set
  • Deprecate then remove the properties
  • Make the properties readonly (breaking change)

I've currently leant towards the first option.

Choose a reason for hiding this comment

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

I see the dilemma. Is it possible to split the property up and only deprecate the write? I only jumped on this as I saw in the android notifier that we were deprecating bits like this as part of similar changes. If it doesn't make sense for bugsnag-cocoa then that is cool

Copy link
Contributor

@kattrali kattrali Jun 15, 2018

Choose a reason for hiding this comment

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

Might be worth trying:

  • Mark both properties as readonly
  • Add setter methods
  • Mark setters as deprecated

/**
* Retrieves the endpoint used to send tracked sessions to Bugsnag
*
* NOTE: it is strongly recommended that you set this value via setEndpointsForNotify:sessions: instead.

Choose a reason for hiding this comment

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

is it worth using something like this to make this as deprecated?

@@ -14,7 +14,7 @@ @implementation BugsnagUser
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
_userId = dict[@"id"];
_emailAddress = dict[@"emailAddress"];
_emailAddress = dict[@"email"];

Choose a reason for hiding this comment

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

are the changes in this file related to this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This addresses an issue in the session request serialisation which was found when adding mazerunner scenarios. Currently the session request sends user.emailAddress rather than user.email, which is corrected in e12ff70.

I can split this off into a separate PR if you think it makes more sense.

Choose a reason for hiding this comment

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

that makes sense, I think it would be good to have it in a separate PR to track it more easily

BugsnagConfiguration *config = [BugsnagConfiguration new];
[config setEndpointsForNotify:@"" sessions:@"http://sessions.example.com"];
XCTFail();
} @catch(NSException * e) {

Choose a reason for hiding this comment

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

is it better to use something like XCTAssertThrowsSpecific here?

BugsnagConfiguration *config = [BugsnagConfiguration new];
[config setEndpointsForNotify:@"http://" sessions:@"http://sessions.example.com"];
XCTFail();
} @catch(NSException * e) {

Choose a reason for hiding this comment

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

is it better to use something like XCTAssertThrowsSpecific here?

@@ -19,7 +19,7 @@ - (void)testDictDeserialisation {

NSDictionary *dict = @{
@"id": @"test",
@"emailAddress": @"fake@example.com",
@"email": @"fake@example.com",

This comment was marked as outdated.

@@ -249,6 +251,22 @@ - (NSDictionary *)sessionApiHeaders {
};
}

- (void)setEndpointsForNotify:(NSString *_Nonnull)notify sessions:(NSString *_Nonnull)sessions {

This comment was marked as resolved.

This comment was marked as resolved.

- (void)testSetEmptyNotifyEndpoint {
BugsnagConfiguration *config = [BugsnagConfiguration new];
XCTAssertThrowsSpecificNamed([config setEndpointsForNotify:@"" sessions:@"http://sessions.example.com"],
NSException, NSInternalInconsistencyException);

This comment was marked as resolved.

Adds a semaphore that synchronises sending of sessions. This prevents the potential case where a
session could be sent multiple times, and passes mazerunner session count scenarios
@fractalwrench
Copy link
Contributor Author

I believe the outstanding issues with this PR are the question of what to do with the notifyURL and sessionURL properties, which it'd be good to get @kattrali's opinion on.

I found a few issues with the existing Cocoa session tracking implementation, which I've addressed in this PR and need review:

  1. BugsnagUser incorrectly serialised the email address as user.emailAddress, whereas the Session Tracking API expects user.email. The fix has been to change the key in the relevant dictionary.

  2. [BugsnagNotifier start] sets a boolean property self.started to indicate when the notifier has been fully initialised. Within this method, [self willEnterForeground:] is called to track an initial session, which resulted in a NOP. The fix here was to set the flag to true before starting the first session.

  3. There appears to be an extra session recorded within [[BugsnagNotifier alloc] init], which has been removed so that only 1 session is captured on initialisation by default.

  4. There is a potential for [BugsnagSessionTracker send] to send the same session multiple times. Although the method uses @synchronize, the underlying BugsnagApiClient uses NSURLSession, which is asynchronous. The proposed fix is to use a semaphore to control access, and to signal within the completion block when the request has finished. I'm not convinced that this is a likely scenario for a real-world application as I've only seen it in mazerunner scenarios which have their own hooks to flush sessions (reducing the time spent waiting) - however, it would be nice to address it.

@fractalwrench fractalwrench removed the wip label Jun 15, 2018
Copy link
Contributor

@kattrali kattrali left a comment

Choose a reason for hiding this comment

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

Looks good overall, had some concerns around concurrency and the test setup.

}

- (void)run {
[self flushAllSessions];
Copy link
Contributor

Choose a reason for hiding this comment

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

By manually calling flush, this doesn't test whether automatically enabled sessions will be sent in a regular app environment. For example, if there is a problem where the session tracker never calls send, the test will pass but the session will never be sent in a release. Rather than calling it directly, the scenario should wait however long is reasonably expected for the session to be sent automatically. The same principle applies to the other scenarios, such as calling startSession; waiting for the expected behavior rather than forcing it to happen immediately reflects actual usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My main concern with this approach is that if we apply it to all the session scenarios, it's going to lead to very long CI build times, as it can take 60s for this to occur. I've updated the scenarios to send sessions naturally for now, but think we'll have to revisit this approach if it gets to the point where we have >5 session scenarios.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you shorten the interval in tests? It sounds similar to something that I did in the tests bugsnag-node.

Copy link
Contributor

Choose a reason for hiding this comment

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

@implementation AutoSessionScenario

- (void)startBugsnag {
self.config.shouldAutoCaptureSessions = YES;
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be unneeded since YES is the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ordinarily this would be true, but the config object passed to the scenario has shouldAutoCaptureSessions set to false. This avoids needing to update all the other scenarios to anticipate an additional request.

@implementation DisabledSessionTrackingScenario

- (void)run {
[self flushAllSessions];

This comment was marked as resolved.

}
dispatch_semaphore_wait(requestSemaphore, DISPATCH_TIME_FOREVER);
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens here if payload.sessions.count == 0 (and the previous block is never triggered)? Might need to be accounted for to prevent a deadlock.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed by sending a signal to the semaphore immediately in this case.

@fractalwrench
Copy link
Contributor Author

Addressed the inline feedback and added a changelog entry. I've decided to make notifyURL and sessionURL readonly, as the suggested approach in the comments wasn't successful.

@kattrali
Copy link
Contributor

Adding - (void)setNotifyURL etc wasn’t successful?

@fractalwrench
Copy link
Contributor Author

@kattrali nope - not in the example Swift project. I attempted adding:

- (void)setNotifyURL:(NSURL *)url {
    _notifyURL = url;
}

Then ran pod install, and attempted to compile config.notifyURL = URL(string: "")

@kattrali
Copy link
Contributor

What did the message say? Could be something that needs to be aliased for swift. If resolving that doesn’t work, it would make more sense to skip setting readonly to avoid the breaking change and keep the setters as deprecated.

@fractalwrench
Copy link
Contributor Author

@kattrali the exact error message is: Error:(30, 42) cannot assign to property: 'notifyURL' is a get-only property

For the following declaration: - (void)setNotifyURL:(NSURL *)url NS_SWIFT_NAME(notifyURL);

It seems to compile ok for Objective-C, although there is a lint warning.

@kattrali
Copy link
Contributor

I see. And if you don’t change the readwrite property but add the setter?

@fractalwrench
Copy link
Contributor Author

@kattrali that seems to work, although there's another warning:

Writable atomic property 'notifyURL' cannot pair a synthesized getter with a user defined setter

I can ignore that and proceed with the above approach if you're happy?

@kattrali
Copy link
Contributor

Haha my happiness is compiler happiness + making usage intent clear :)

Does defining the getter + making the setter deprecated resolve everything?

@fractalwrench
Copy link
Contributor Author

@kattrali for Objective-C yes, that resolves the issues, but the deprecation notice isn't shown in Swift.

@fractalwrench
Copy link
Contributor Author

After discussing this with @snmaynard we decided that of the options below, (2) would be the best:

  1. Keep the properties as readwrite and document not to use them
  2. Make the properties readonly (breaking change)
  3. Deprecate the properties entirely

I'm not aware of anything else outstanding on this PR.

@@ -39,7 +39,7 @@ - (void)setUp {
config.autoNotify = NO;
config.apiKey = @"apiKeyHere";
config.releaseStage = @"MagicalTestingTime";
config.notifyURL = nil;
[config setEndpointsForNotify:@"http://localhost:8000" sessions:@"http://localhost:8000"];
Copy link

Choose a reason for hiding this comment

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

The notifyURL was cleared previous, but now this is being set to localhost:8000, is this correct? Also sessions endpoint is localhost:10000

@fractalwrench fractalwrench merged commit dbc380f into next Jun 21, 2018
@fractalwrench fractalwrench deleted the on-by-default-session-tracking branch June 21, 2018 09:48
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.

5 participants