Skip to content

Commit 7d746c3

Browse files
fix: Clean up old envelopes (#2322)
We now clean up old envelopes after 90 days. So when changing DSN for example, old envelopes don't linger around forever and ever.
1 parent 0979ac6 commit 7d746c3

File tree

4 files changed

+124
-16
lines changed

4 files changed

+124
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Fixes
1010

1111
- Stop profiler when app moves to background (#2331)
12+
- Clean up old envelopes (#2322)
1213

1314
## 7.29.0
1415

Sources/Sentry/SentryFileManager.m

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#import "NSDate+SentryExtras.h"
33
#import "SentryAppState.h"
44
#import "SentryDataCategoryMapper.h"
5+
#import "SentryDependencyContainer.h"
6+
#import "SentryDispatchQueueWrapper.h"
57
#import "SentryDsn.h"
68
#import "SentryEnvelope.h"
79
#import "SentryEvent.h"
@@ -17,6 +19,7 @@
1719
SentryFileManager ()
1820

1921
@property (nonatomic, strong) id<SentryCurrentDateProvider> currentDateProvider;
22+
@property (nonatomic, copy) NSString *basePath;
2023
@property (nonatomic, copy) NSString *sentryPath;
2124
@property (nonatomic, copy) NSString *eventsPath;
2225
@property (nonatomic, copy) NSString *envelopesPath;
@@ -37,6 +40,17 @@ @implementation SentryFileManager
3740
- (nullable instancetype)initWithOptions:(SentryOptions *)options
3841
andCurrentDateProvider:(id<SentryCurrentDateProvider>)currentDateProvider
3942
error:(NSError **)error
43+
{
44+
return [self initWithOptions:options
45+
andCurrentDateProvider:currentDateProvider
46+
dispatchQueueWrapper:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper
47+
error:error];
48+
}
49+
50+
- (nullable instancetype)initWithOptions:(SentryOptions *)options
51+
andCurrentDateProvider:(id<SentryCurrentDateProvider>)currentDateProvider
52+
dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
53+
error:(NSError **)error
4054
{
4155
self = [super init];
4256
if (self) {
@@ -49,9 +63,9 @@ - (nullable instancetype)initWithOptions:(SentryOptions *)options
4963

5064
SENTRY_LOG_DEBUG(@"SentryFileManager.cachePath: %@", cachePath);
5165

52-
self.sentryPath = [cachePath stringByAppendingPathComponent:@"io.sentry"];
66+
self.basePath = [cachePath stringByAppendingPathComponent:@"io.sentry"];
5367
self.sentryPath =
54-
[self.sentryPath stringByAppendingPathComponent:[options.parsedDsn getHash]];
68+
[self.basePath stringByAppendingPathComponent:[options.parsedDsn getHash]];
5569

5670
if (![fileManager fileExistsAtPath:self.sentryPath]) {
5771
[self.class createDirectoryAtPath:self.sentryPath withError:error];
@@ -81,6 +95,9 @@ - (nullable instancetype)initWithOptions:(SentryOptions *)options
8195

8296
self.currentFileCounter = 0;
8397
self.maxEnvelopes = options.maxCacheItems;
98+
99+
[dispatchQueueWrapper dispatchAfter:10
100+
block:^{ [self deleteOldEnvelopesFromAllSentryPaths]; }];
84101
}
85102
return self;
86103
}
@@ -93,7 +110,6 @@ - (void)setDelegate:(id<SentryFileManagerDelegate>)delegate
93110
- (void)deleteAllFolders
94111
{
95112
NSFileManager *fileManager = [NSFileManager defaultManager];
96-
[fileManager removeItemAtPath:self.envelopesPath error:nil];
97113
[fileManager removeItemAtPath:self.sentryPath error:nil];
98114
}
99115

@@ -158,6 +174,47 @@ - (SentryFileContents *_Nullable)getFileContents:(NSString *)folderPath
158174
}
159175
}
160176

177+
// Delete every envelope in self.basePath older than 90 days,
178+
// as Sentry only retains data for 90 days.
179+
- (void)deleteOldEnvelopesFromAllSentryPaths
180+
{
181+
// First we find all directories in the base path, these are all the various hashed DSN paths
182+
for (NSString *path in [self allFilesInFolder:self.basePath]) {
183+
NSString *fullPath = [self.basePath stringByAppendingPathComponent:path];
184+
NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath
185+
error:nil];
186+
if (!dict || dict[NSFileType] != NSFileTypeDirectory) {
187+
SENTRY_LOG_DEBUG(@"Could not get NSFileTypeDirectory from %@", fullPath);
188+
continue;
189+
}
190+
191+
// Then we will remove all old items from the envelopes subdirectory
192+
[self deleteOldEnvelopesFromPath:[fullPath stringByAppendingPathComponent:@"envelopes"]];
193+
}
194+
}
195+
196+
- (void)deleteOldEnvelopesFromPath:(NSString *)envelopesPath
197+
{
198+
NSTimeInterval now = [[self.currentDateProvider date] timeIntervalSince1970];
199+
200+
for (NSString *path in [self allFilesInFolder:envelopesPath]) {
201+
NSString *fullPath = [envelopesPath stringByAppendingPathComponent:path];
202+
NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath
203+
error:nil];
204+
if (!dict || !dict[NSFileCreationDate]) {
205+
SENTRY_LOG_DEBUG(@"Could not get NSFileCreationDate from %@", fullPath);
206+
continue;
207+
}
208+
209+
NSTimeInterval age = now - [dict[NSFileCreationDate] timeIntervalSince1970];
210+
if (age > 90 * 24 * 60 * 60) {
211+
[self removeFileAtPath:fullPath];
212+
SENTRY_LOG_DEBUG(
213+
@"Removed envelope at path %@ because it was older than 90 days", fullPath);
214+
}
215+
}
216+
}
217+
161218
- (void)deleteAllEnvelopes
162219
{
163220
for (NSString *path in [self allFilesInFolder:self.envelopesPath]) {
@@ -171,10 +228,7 @@ - (void)deleteAllEnvelopes
171228
NSError *error = nil;
172229
NSArray<NSString *> *storedFiles = [fileManager contentsOfDirectoryAtPath:path error:&error];
173230
if (nil != error) {
174-
[SentryLog
175-
logWithMessage:[NSString stringWithFormat:@"Couldn't load files in folder %@: %@", path,
176-
error]
177-
andLevel:kSentryLevelError];
231+
SENTRY_LOG_ERROR(@"Couldn't load files in folder %@: %@", path, error);
178232
return [NSArray new];
179233
}
180234
return [storedFiles sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];

Sources/Sentry/include/SentryFileManager.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ NS_ASSUME_NONNULL_BEGIN
77

88
@protocol SentryFileManagerDelegate;
99

10-
@class SentryEvent, SentryOptions, SentryEnvelope, SentryFileContents, SentryAppState;
10+
@class SentryEvent, SentryOptions, SentryEnvelope, SentryFileContents, SentryAppState,
11+
SentryDispatchQueueWrapper;
1112

1213
NS_SWIFT_NAME(SentryFileManager)
1314
@interface SentryFileManager : NSObject
@@ -17,6 +18,11 @@ SENTRY_NO_INIT
1718

1819
- (nullable instancetype)initWithOptions:(SentryOptions *)options
1920
andCurrentDateProvider:(id<SentryCurrentDateProvider>)currentDateProvider
21+
error:(NSError **)error;
22+
23+
- (nullable instancetype)initWithOptions:(SentryOptions *)options
24+
andCurrentDateProvider:(id<SentryCurrentDateProvider>)currentDateProvider
25+
dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
2026
error:(NSError **)error NS_DESIGNATED_INITIALIZER;
2127

2228
- (void)setDelegate:(id<SentryFileManagerDelegate>)delegate;
@@ -37,7 +43,6 @@ SENTRY_NO_INIT
3743
+ (BOOL)createDirectoryAtPath:(NSString *)path withError:(NSError **)error;
3844

3945
- (void)deleteAllEnvelopes;
40-
4146
- (void)deleteAllFolders;
4247

4348
/**

Tests/SentryTests/Helper/SentryFileManagerTests.swift

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class SentryFileManagerTests: XCTestCase {
1414
let eventIds: [SentryId]
1515

1616
let currentDateProvider: TestCurrentDateProvider!
17+
let dispatchQueueWrapper: TestSentryDispatchQueueWrapper!
1718

1819
let options: Options
1920

@@ -33,6 +34,8 @@ class SentryFileManagerTests: XCTestCase {
3334

3435
init() {
3536
currentDateProvider = TestCurrentDateProvider()
37+
dispatchQueueWrapper = TestSentryDispatchQueueWrapper()
38+
dispatchQueueWrapper.dispatchAfterExecutesBlock = true
3639

3740
eventIds = (0...(maxCacheItems + 10)).map { _ in SentryId() }
3841

@@ -60,14 +63,14 @@ class SentryFileManagerTests: XCTestCase {
6063
}
6164

6265
func getSut() throws -> SentryFileManager {
63-
let sut = try SentryFileManager(options: options, andCurrentDateProvider: currentDateProvider)
66+
let sut = try SentryFileManager(options: options, andCurrentDateProvider: currentDateProvider, dispatchQueueWrapper: dispatchQueueWrapper)
6467
sut.setDelegate(delegate)
6568
return sut
6669
}
6770

6871
func getSut(maxCacheItems: UInt) throws -> SentryFileManager {
6972
options.maxCacheItems = maxCacheItems
70-
let sut = try SentryFileManager(options: options, andCurrentDateProvider: currentDateProvider)
73+
let sut = try SentryFileManager(options: options, andCurrentDateProvider: currentDateProvider, dispatchQueueWrapper: dispatchQueueWrapper)
7174
sut.setDelegate(delegate)
7275
return sut
7376
}
@@ -107,8 +110,8 @@ class SentryFileManagerTests: XCTestCase {
107110
sut.storeCurrentSession(SentrySession(releaseName: "1.0.0"))
108111
sut.storeTimestampLast(inForeground: Date())
109112

110-
_ = try SentryFileManager(options: fixture.options, andCurrentDateProvider: TestCurrentDateProvider())
111-
let fileManager = try SentryFileManager(options: fixture.options, andCurrentDateProvider: TestCurrentDateProvider())
113+
_ = try SentryFileManager(options: fixture.options, andCurrentDateProvider: TestCurrentDateProvider(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper())
114+
let fileManager = try SentryFileManager(options: fixture.options, andCurrentDateProvider: TestCurrentDateProvider(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper())
112115

113116
XCTAssertEqual(1, fileManager.getAllEnvelopes().count)
114117
XCTAssertNotNil(fileManager.readCurrentSession())
@@ -118,15 +121,15 @@ class SentryFileManagerTests: XCTestCase {
118121
func testInitDeletesEventsFolder() throws {
119122
storeEvent()
120123

121-
_ = try SentryFileManager(options: fixture.options, andCurrentDateProvider: TestCurrentDateProvider())
124+
_ = try SentryFileManager(options: fixture.options, andCurrentDateProvider: TestCurrentDateProvider(), dispatchQueueWrapper: TestSentryDispatchQueueWrapper())
122125

123126
assertEventFolderDoesntExist()
124127
}
125128

126129
func testInitDoesntCreateEventsFolder() {
127130
assertEventFolderDoesntExist()
128131
}
129-
132+
130133
func testStoreEnvelope() throws {
131134
let envelope = TestConstants.envelope
132135
sut.store(envelope)
@@ -139,7 +142,52 @@ class SentryFileManagerTests: XCTestCase {
139142
let actualData = envelopes[0].contents
140143
XCTAssertEqual(expectedData, actualData as Data)
141144
}
142-
145+
146+
func testDeleteOldEnvelopes() throws {
147+
let envelope = TestConstants.envelope
148+
let path = sut.store(envelope)
149+
150+
let timeIntervalSince1970 = fixture.currentDateProvider.date().timeIntervalSince1970 - (90 * 24 * 60 * 60)
151+
let date = Date(timeIntervalSince1970: timeIntervalSince1970 - 1)
152+
try FileManager.default.setAttributes([FileAttributeKey.creationDate: date], ofItemAtPath: path)
153+
154+
XCTAssertEqual(sut.getAllEnvelopes().count, 1)
155+
156+
sut = try fixture.getSut()
157+
158+
XCTAssertEqual(sut.getAllEnvelopes().count, 0)
159+
}
160+
161+
func testDontDeleteYoungEnvelopes() throws {
162+
let envelope = TestConstants.envelope
163+
let path = sut.store(envelope)
164+
165+
let timeIntervalSince1970 = fixture.currentDateProvider.date().timeIntervalSince1970 - (90 * 24 * 60 * 60)
166+
let date = Date(timeIntervalSince1970: timeIntervalSince1970)
167+
try FileManager.default.setAttributes([FileAttributeKey.creationDate: date], ofItemAtPath: path)
168+
169+
XCTAssertEqual(sut.getAllEnvelopes().count, 1)
170+
171+
sut = try fixture.getSut()
172+
173+
XCTAssertEqual(sut.getAllEnvelopes().count, 1)
174+
}
175+
176+
func testDontDeleteYoungEnvelopesFromOldEnvelopesFolder() throws {
177+
let envelope = TestConstants.envelope
178+
sut.store(envelope)
179+
180+
let timeIntervalSince1970 = fixture.currentDateProvider.date().timeIntervalSince1970 - (90 * 24 * 60 * 60)
181+
let date = Date(timeIntervalSince1970: timeIntervalSince1970)
182+
try FileManager.default.setAttributes([FileAttributeKey.creationDate: date], ofItemAtPath: sut.envelopesPath)
183+
184+
XCTAssertEqual(sut.getAllEnvelopes().count, 1)
185+
186+
sut = try fixture.getSut()
187+
188+
XCTAssertEqual(sut.getAllEnvelopes().count, 1)
189+
}
190+
143191
func testCreateDirDoesNotThrow() throws {
144192
try SentryFileManager.createDirectory(atPath: "a")
145193
}

0 commit comments

Comments
 (0)