Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
68562a6
NR-489823 working start and stop msr, need to make changes when error…
mbruin-NR Nov 25, 2025
555241e
NR-489823 fix for setUserId session end
mbruin-NR Nov 26, 2025
332a40e
NR-489823 fix tvOS and watchOS
mbruin-NR Nov 26, 2025
718cd93
NR-489823 using the same function to check session replay and end / h…
mbruin-NR Dec 2, 2025
cc148b4
Merge branch 'develop' into NR-489823
mbruin-NR Dec 2, 2025
e54534d
Nr-455916: initial pruning and 15 second buff strat
cdillard-NewRelic Dec 2, 2025
f99f6f9
Merge branch 'NR-489823' of github.com:newrelic/newrelic-ios-agent in…
cdillard-NewRelic Dec 2, 2025
170c0e6
Merge branch 'develop' of github.com:newrelic/newrelic-ios-agent into…
cdillard-NewRelic Dec 4, 2025
bcc7232
NR-455916 mode transition and prune
cdillard-NewRelic Dec 10, 2025
2c06c39
f
cdillard-NewRelic Dec 11, 2025
8c09837
Merge branch 'develop' of github.com:newrelic/newrelic-ios-agent into…
cdillard-NewRelic Dec 15, 2025
3b4a5aa
Merge branch 'develop' of github.com:newrelic/newrelic-ios-agent into…
cdillard-NewRelic Dec 15, 2025
37bf31e
NR-455916: fix
cdillard-NewRelic Dec 15, 2025
1106fa8
fix tvOS
cdillard-NewRelic Dec 15, 2025
b51311f
fix 2
cdillard-NewRelic Dec 15, 2025
d0971f5
fix:ed the platform integrations
cdillard-NewRelic Dec 16, 2025
b683d06
fix: one more test fix
cdillard-NewRelic Dec 16, 2025
6d4f162
fix: 3rd
cdillard-NewRelic Dec 16, 2025
5f8571e
update tvOS
cdillard-NewRelic Dec 16, 2025
2e58eec
NR-455916: address cr feedback, remove error buffer, use index window
cdillard-NewRelic Dec 19, 2025
7240d33
fix 4
cdillard-NewRelic Dec 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions Agent.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Agent/Analytics/PersistentEventStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ - (void)setObject:(nonnull id)object forKey:(nonnull id)key {
// NRLOG_AUDIT(@"Entered block");
@synchronized (self) {
if(!self->_dirty) {
NRLOG_AUDIT(@"Not writing file because it's not dirty");
NRLOG_AGENT_DEBUG(@"Not writing file because it's not dirty");
return;
}
}
Expand All @@ -108,7 +108,7 @@ - (void)removeObjectForKey:(id)key {
// NRLOG_AGENT_VERBOSE(@"Entered Remove Block");
@synchronized (self) {
if(!self->_dirty) {
NRLOG_AGENT_VERBOSE(@"Not writing removed item file because it's not dirty");
NRLOG_AGENT_DEBUG(@"Not writing removed item file because it's not dirty");
return;
}
}
Expand All @@ -132,7 +132,7 @@ - (void)clearAll {
// NRLOG_AGENT_VERBOSE(@"Entered Clear Block");
@synchronized (self) {
if(!self->_dirty) {
// NRLOG_AGENT_VERBOSE(@"Not writing cleared file because it's not dirty");
// NRLOG_AGENT_DEBUG(@"Not writing cleared file because it's not dirty");
return;
}
}
Expand Down Expand Up @@ -182,7 +182,7 @@ - (void)saveToFile {
options:NSDataWritingAtomic
error:&error];
if(!success) {
NRLOG_AGENT_ERROR(@"Error saving data: %@", error.description);
NRLOG_AGENT_DEBUG(@"Error saving data: %@", error.description);
} else {
// NRLOG_AUDIT(@"Wrote file");
_lastSave = [NSDate new];
Expand Down
3 changes: 3 additions & 0 deletions Agent/General/NewRelicAgentInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void) sessionReplayEndSession;

- (BOOL) isSessionReplaySampled;
- (BOOL) isSessionReplayErrorSampled;

- (BOOL) isSessionReplayEnabled;

Expand Down Expand Up @@ -124,6 +125,8 @@ NS_ASSUME_NONNULL_BEGIN

// Pause a session replay recording
- (BOOL) pauseReplay;
// Notify Session Replay of an error
- (void)sessionReplayOnError:(NSError *_Nullable)error;

// END SESSION REPLAY SECTION Methods to start and pause SessionReplay

Expand Down
104 changes: 89 additions & 15 deletions Agent/General/NewRelicAgentInternal.m
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ - (void) initialize {
}
#endif
#if !TARGET_OS_TV && !TARGET_OS_WATCH
// SESSION REPLAY INITIALIZATION
if (@available(iOS 13.0, *)) {
SessionReplayReporter *reporter = [[SessionReplayReporter alloc] initWithApplicationToken:_agentConfiguration.applicationToken.value url: [self->_agentConfiguration sessionReplayURL]];
_sessionReplay = [[SessionReplayManager alloc] initWithReporter:reporter url: [self->_agentConfiguration sessionReplayURL]];
Expand All @@ -366,6 +367,9 @@ - (void) initialize {
[_sessionReplay checkForPreviousSessionFiles];
}
}

// END SESSION REPLAY INITIALIZATION

#endif
}

Expand Down Expand Up @@ -616,22 +620,39 @@ - (void) onSessionStart {

- (void) sessionReplayEndSession {
#if !TARGET_OS_TV && !TARGET_OS_WATCH
BOOL isSampled = [self isSessionReplaySampled];
// ERROR MODE
// BOOL isSampled = [self isSessionReplaySampled];
BOOL isEnabled = [self isSessionReplayEnabled];

if ((isSampled && isEnabled) || (_sessionReplay.isManuallyActive && isEnabled) || self->_sessionReplay.isRunning) {
if ((isEnabled) || (_sessionReplay.isManuallyActive && isEnabled) || self->_sessionReplay.isRunning) {

// ERROR MODE
// if ((isSampled && isEnabled) || (_sessionReplay.isManuallyActive && isEnabled) || self->_sessionReplay.isRunning) {
[_sessionReplay endSessionWithHarvest:isEnabled];
}
#endif
}

- (void) sessionReplayStart {
#if !TARGET_OS_TV && !TARGET_OS_WATCH
// START ERROR MODE CHANGE

// should isSampled matter when deciding to start or not in every case?

BOOL isSampled = [self isSessionReplaySampled];
if (isSampled && [self isSessionReplayEnabled]) {
[_sessionReplay startFromManual:FALSE];
@synchronized(kNRMAAnalyticsInitializationLock) {
[self.analyticsController setNRSessionAttribute:kNRMA_RA_hasReplay value:[[NRMABool alloc] initWithBOOL:YES]];
// always start session replay if its error or full
// ERROR MODE
if ([self isSessionReplayEnabled]) {
SessionReplayRecordingMode m = [self determineRecordingMode];

NRLOG_AGENT_DEBUG(@"Starting session replay with mode: %@", (m == SessionReplayRecordingModeFull) ? @"Full" : (m == SessionReplayRecordingModeError) ? @"Error" : @"Off");

if (m != SessionReplayRecordingModeOff) {

[_sessionReplay startFromManual:FALSE with:m];
@synchronized(kNRMAAnalyticsInitializationLock) {
[self.analyticsController setNRSessionAttribute:kNRMA_RA_hasReplay value:[[NRMABool alloc] initWithBOOL:YES]];
}
}
}
#endif
Expand Down Expand Up @@ -685,11 +706,20 @@ - (BOOL) pauseReplay {
return false;
}

static const NSString* kNRMA_BGFG_MUTEX = @"com.newrelic.bgfg.mutex";
static const NSString* kNRMA_APPLICATION_WILL_TERMINATE = @"com.newrelic.appWillTerm";
- (void)sessionReplayOnError:(NSError *_Nullable)error {
#if !TARGET_OS_TV && !TARGET_OS_WATCH
if (_sessionReplay != nil) {
[_sessionReplay onError:error];
}
#endif
}

- (void) applicationWillEnterForeground {
static const NSString *kNRMA_BGFG_MUTEX = @"com.newrelic.bgfg.mutex";
static const NSString *kNRMA_APPLICATION_WILL_TERMINATE =
@"com.newrelic.appWillTerm";

- (void)applicationWillEnterForeground {

if (_isShutdown) {
return;
}
Expand Down Expand Up @@ -775,8 +805,11 @@ - (void) sessionStartInitialization {

#if !TARGET_OS_TV && !TARGET_OS_WATCH
if (@available(iOS 13.0, *)) {
BOOL isSampled = [self isSessionReplaySampled];
if (isSampled && [self isSessionReplayEnabled]) {
// ERROR MODE
// BOOL isSampled = [self isSessionReplaySampled];
if ([self isSessionReplayEnabled]) {

//if (isSampled && [self isSessionReplayEnabled]) {
[self.analyticsController setNRSessionAttribute:kNRMA_RA_hasReplay value:[[NRMABool alloc] initWithBOOL:YES]];
}
}
Expand Down Expand Up @@ -1273,6 +1306,9 @@ - (BOOL)isClassNameUnmasked:(NSString *)className {
return isUnmasked;
}


// SANPLING AND ERRORED SESSION SESSION REPLAY

- (void) makeSampleSeeds {
NRLOG_AGENT_DEBUG(@"config: RESEEDING");

Expand All @@ -1292,24 +1328,62 @@ - (BOOL) isSessionReplaySampled {
sampleRate = [NRMAHarvestController configuration].session_replay_sampling_rate;
}
else {
// NRLOG_AGENT_DEBUG(@"isSessionReplaySampled using default rate of 100.0");
NRLOG_AGENT_DEBUG(@"isSessionReplaySampled using default rate of 100.0");
}
//NRLOG_AGENT_DEBUG(@"isSessionReplaySampled session replay config: Sampling decision: %d, because seed <= rate: %f <= %f", (self.sessionReplaySampleSeed <= sampleRate), [[NewRelicAgentInternal sharedInstance] sessionReplaySampleSeed], sampleRate);
NRLOG_AGENT_DEBUG(@"isSessionReplaySampled session replay config: Sampling decision: %d, because seed <= rate: %f <= %f", (self.sessionReplaySampleSeed <= sampleRate), [[NewRelicAgentInternal sharedInstance] sessionReplaySampleSeed], sampleRate);

return (self.sessionReplaySampleSeed <= sampleRate);
}

- (BOOL) isSessionReplayErrorSampled {
double errorSampleRate = 0.0;
if ( [NRMAHarvestController configuration] != nil) {
errorSampleRate = [NRMAHarvestController configuration].session_replay_error_sampling_rate;
}
else {
NRLOG_AGENT_DEBUG(@"isSessionReplaErrorSampled using default rate of 100.0");
}
NRLOG_AGENT_DEBUG(@"isSessionReplaErrorSampled session replay config: ERROR Sampling decision: %d, because seed <= rate: %f <= %f", (self.sessionReplayErrorSampleSeed <= errorSampleRate), [[NewRelicAgentInternal sharedInstance] sessionReplayErrorSampleSeed], errorSampleRate);

return (self.sessionReplayErrorSampleSeed <= errorSampleRate);
}

- (BOOL) isSessionReplayEnabled {
BOOL isEnabled = false;
if ( [NRMAHarvestController configuration] != nil) {
isEnabled = [NRMAHarvestController configuration].session_replay_enabled;
}
else {
//NRLOG_AGENT_DEBUG(@"isSessionReplayEnabled using default value of false");
// NRLOG_AGENT_DEBUG(@"isSessionReplayEnabled using default value of false");
}
//NRLOG_AGENT_DEBUG(@"isSessionReplayEnabled using value: %@", isEnabled ? @"true" : @"false");
// NRLOG_AGENT_DEBUG(@"isSessionReplayEnabled using value: %@", isEnabled ? @"true" : @"false");

return isEnabled;
}

/**
* Determines the appropriate SessionReplay recording mode based on the sampling configuration.
*
* Logic:
* - If isSampled() is true: use FULL mode (continuous recording and sending)
* - Else if isErrorSampled() is true: use ERROR mode (buffering only, send on error)
* - Otherwise: use OFF mode (no recording)
*
* @param config The SessionReplayConfiguration with sampling rates
* @return The determined SessionReplayMode
*/
- (SessionReplayRecordingMode) determineRecordingMode {
if ([self isSessionReplaySampled]) {
return SessionReplayRecordingModeFull;
}
else if ([self isSessionReplayErrorSampled]) {
return SessionReplayRecordingModeError;
}
else {
return SessionReplayRecordingModeOff;
}
}

// END SAMPLING AND ERRORED SESSION SESSION REPLAY

@end
3 changes: 3 additions & 0 deletions Agent/HandledException/NRMAHandledExceptions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#import "Constants.h"
#import "NRMAAttributeValidator.h"

// Session Replay Error Sampling
// END

@interface NRMAAnalytics(Protected)
// Because the NRMAAnalytics class interfaces with non Objective-C++ files, we cannot expose the API on the header. Therefore, we must use this reference.
- (std::shared_ptr<NewRelic::AnalyticsController>&) analyticsController;
Expand Down
1 change: 1 addition & 0 deletions Agent/Harvester/DataStore/NRMAAgentConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
@property(readonly,strong) NSString* loggingHost;
@property(readonly,strong) NSString* loggingURL;
@property(readonly,strong) NSString* sessionReplayURL;
@property(readonly,strong) NSString* sessionReplayMode;

@property(readonly,strong) NRMAAppToken* applicationToken;
@property(atomic,strong) NSString* sessionIdentifier;
Expand Down
5 changes: 4 additions & 1 deletion Agent/Harvester/DataStore/NRMAAgentConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ - (id) initWithAppToken:(NRMAAppToken*)token
[self setCollectorHost:collectorHost];
[self setCrashCollectorHost:crashHost];
[self setLoggingURL];


// Default mode
_sessionReplayMode = @"OFF";

if ([[NSProcessInfo processInfo] environment][@"UITesting"]) {
_useSSL = NO;
} else {
Expand Down
1 change: 1 addition & 0 deletions Agent/Harvester/DataStore/NRMAHarvesterConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
@property(nonatomic,assign) BOOL session_replay_enabled;
@property(nonatomic,assign) double session_replay_sampling_rate;
@property(nonatomic,assign) double session_replay_error_sampling_rate;
// MASKING MODE
@property(nonatomic,assign) NSString* session_replay_mode;

@property(nonatomic,assign) BOOL session_replay_maskApplicationText;
Expand Down
7 changes: 5 additions & 2 deletions Agent/Harvester/DataStore/NRMAHarvesterConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,9 @@ - (BOOL) isEqual:(id)object {

// session replay equality
if (self.session_replay_sampling_rate != that.session_replay_sampling_rate) return NO;

if (self.session_replay_error_sampling_rate != that.session_replay_error_sampling_rate) return NO;

if (![self.session_replay_mode isEqualToString:that.session_replay_mode]) return NO;
if (self.session_replay_enabled != that.session_replay_enabled) return NO;
if (self.has_session_replay_config != that.has_session_replay_config) return NO;
Expand Down Expand Up @@ -622,10 +625,10 @@ - (NSUInteger) hash


// session replay

// result = 31 * result + self.log_reporting_level.hash;
result = 31 * result + self.session_replay_enabled;
result = 31 * result + self.session_replay_sampling_rate;
result = 31 * result + self.session_replay_error_sampling_rate;

result = 31 * result + self.session_replay_mode.hash;

return result;
Expand Down
4 changes: 2 additions & 2 deletions Agent/Network/NRMAHTTPUtilities.mm
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ + (NRMAPayloadContainer *) generatePayload {
payload = NewRelic::Connectivity::Facade::getInstance().startTrip();

if(payload == nullptr) {
NRLOG_DEBUG(@"Not attaching distributed tracing because account or application id are missing.");
NRLOG_AGENT_DEBUG(@"Not attaching distributed tracing because account or application id are missing.");
return nil;
}
payload->setDistributedTracing(true);
Expand All @@ -196,7 +196,7 @@ + (NRMAPayloadContainer *) generatePayload {

+ (NRMAPayload *) startTrip {
if(!NewRelic::Application::getInstance().isValid()) {
NRLOG_DEBUG(@"Not attaching distributed tracing because account or application id are missing.");
NRLOG_AGENT_DEBUG(@"Not attaching distributed tracing because account or application id are missing.");
return nil;
}

Expand Down
Loading