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: Add extension for Data to track file I/O operations with Sentry #4862

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add extension for `Data` to track file I/O operations with Sentry (#4862)

## 8.45.0

### Features
Expand Down
20 changes: 20 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@
D42E48572D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48562D48DF1600D251BC /* SentryBuildAppStartSpansTests.swift */; };
D43647EF2D5CF9E3001468E0 /* SentrySpanDataKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43647EE2D5CF9DC001468E0 /* SentrySpanDataKey.swift */; };
D43647F12D5CFB71001468E0 /* SentrySpanKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43647F02D5CFB71001468E0 /* SentrySpanKeyTests.swift */; };
D468C0622D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */; };
D48724DB2D352597005DE483 /* SentryTraceOrigin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */; };
D48724DD2D354939005DE483 /* SentrySpanOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DC2D354934005DE483 /* SentrySpanOperation.swift */; };
D48724E02D3549CA005DE483 /* SentrySpanOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */; };
Expand All @@ -822,8 +823,10 @@
D4AF00212D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */; };
D4AF00232D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */; };
D4AF00252D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */; };
D4C5F59A2D4249E6002A9BF6 /* DataSentryTracingIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */; };
D4E3F35D2D4A864600F79E2B /* SentryNSDictionarySanitizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42E48582D48FC8F00D251BC /* SentryNSDictionarySanitizeTests.swift */; };
D4E3F35E2D4A877300F79E2B /* SentryNSDictionarySanitize+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = D41909942D490006002B83D0 /* SentryNSDictionarySanitize+Tests.m */; };
D4EDF9842D0B2A210071E7B3 /* Data+SentryTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4EDF9832D0B2A1D0071E7B3 /* Data+SentryTracing.swift */; };
D4F2B5352D0C69D500649E42 /* SentryCrashCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F2B5342D0C69D100649E42 /* SentryCrashCTests.swift */; };
D8019910286B089000C277F0 /* SentryCrashReportSinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D801990F286B089000C277F0 /* SentryCrashReportSinkTests.swift */; };
D802994E2BA836EF000F0081 /* SentryOnDemandReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D802994D2BA836EF000F0081 /* SentryOnDemandReplay.swift */; };
Expand Down Expand Up @@ -1962,6 +1965,7 @@
D46D45EA2D5F412100A1CB35 /* SentryTests_Base.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = SentryTests_Base.xctestplan; path = Plans/SentryTests_Base.xctestplan; sourceTree = SOURCE_ROOT; };
D43647EE2D5CF9DC001468E0 /* SentrySpanDataKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanDataKey.swift; sourceTree = "<group>"; };
D43647F02D5CFB71001468E0 /* SentrySpanKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentrySpanKeyTests.swift; sourceTree = "<group>"; };
D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryFileIOTracker+SwiftHelpers.swift"; sourceTree = "<group>"; };
D48724DA2D352591005DE483 /* SentryTraceOrigin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceOrigin.swift; sourceTree = "<group>"; };
D48724DC2D354934005DE483 /* SentrySpanOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperation.swift; sourceTree = "<group>"; };
D48724DF2D3549C6005DE483 /* SentrySpanOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySpanOperationTests.swift; sourceTree = "<group>"; };
Expand All @@ -1979,6 +1983,8 @@
D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzling.m; sourceTree = "<group>"; };
D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSFileManagerSwizzling.h; path = include/SentryNSFileManagerSwizzling.h; sourceTree = "<group>"; };
D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzlingTests.m; sourceTree = "<group>"; };
D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSentryTracingIntegrationTests.swift; sourceTree = "<group>"; };
D4EDF9832D0B2A1D0071E7B3 /* Data+SentryTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+SentryTracing.swift"; sourceTree = "<group>"; };
D4F2B5342D0C69D100649E42 /* SentryCrashCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashCTests.swift; sourceTree = "<group>"; };
D800942628F82F3A005D3943 /* SwiftDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDescriptor.swift; sourceTree = "<group>"; };
D801990F286B089000C277F0 /* SentryCrashReportSinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashReportSinkTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3826,6 +3832,15 @@
path = Plans;
sourceTree = "<group>";
};
D468C0602D36699700964230 /* IO */ = {
isa = PBXGroup;
children = (
D4EDF9832D0B2A1D0071E7B3 /* Data+SentryTracing.swift */,
D468C0612D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift */,
);
path = IO;
sourceTree = "<group>";
};
D48724D92D35258A005DE483 /* Transactions */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4048,6 +4063,7 @@
D8739CF72BECFF92007D2F66 /* Performance */ = {
isa = PBXGroup;
children = (
D468C0602D36699700964230 /* IO */,
6294287F2CB3BF4E002C454C /* SwizzleClassNameExclude.swift */,
D8739CF82BECFFB5007D2F66 /* SentryTransactionNameSource.swift */,
);
Expand All @@ -4057,6 +4073,7 @@
D875ED09276CC83200422FAC /* IO */ = {
isa = PBXGroup;
children = (
D4C5F5992D4249E0002A9BF6 /* DataSentryTracingIntegrationTests.swift */,
D875ED0A276CC84700422FAC /* SentryFileIOTrackerTests.swift */,
D8CE69BB277E39C700C6EC5C /* SentryFileIOTrackingIntegrationObjCTests.m */,
D885266327739D01001269FC /* SentryFileIOTrackingIntegrationTests.swift */,
Expand Down Expand Up @@ -5063,6 +5080,7 @@
D84D2CC32C29AD120011AF8A /* SentrySessionReplay.swift in Sources */,
849B8F9B2C6E906900148E1F /* SentryUserFeedbackIntegrationDriver.swift in Sources */,
63FE70DF20DA4C1000CDBAE8 /* SentryCrashMonitorType.c in Sources */,
D468C0622D3669A200964230 /* SentryFileIOTracker+SwiftHelpers.swift in Sources */,
7BF9EF7E2722B91F00B5BBEF /* SentryDefaultObjCRuntimeWrapper.m in Sources */,
7BC3936E25B1AB72004F03D3 /* SentryLevelMapper.m in Sources */,
6304360B1EC0595B00C4D3FA /* SentryNSDataUtils.m in Sources */,
Expand All @@ -5082,6 +5100,7 @@
7BB65501253DC1B500887E87 /* SentryUserFeedback.m in Sources */,
7D5C441A237C2E1F00DAB0A3 /* SentrySDK.m in Sources */,
7D65260E237F649E00113EA2 /* SentryScope.m in Sources */,
D4EDF9842D0B2A210071E7B3 /* Data+SentryTracing.swift in Sources */,
84281C472A57905700EE88F2 /* SentrySample.m in Sources */,
84AC61D329F7541E009EEF61 /* SentryDispatchSourceWrapper.m in Sources */,
62A456E52B0370E0003F19A1 /* SentryUIEventTrackerTransactionMode.m in Sources */,
Expand Down Expand Up @@ -5381,6 +5400,7 @@
8E70B0FD25CB72BE002B3155 /* SentrySpanTests.swift in Sources */,
7BBD188F2448469A00427C76 /* HttpDateFormatter.swift in Sources */,
63FE720C20DA66EC00CDBAE8 /* SentryCrashMonitor_Tests.m in Sources */,
D4C5F59A2D4249E6002A9BF6 /* DataSentryTracingIntegrationTests.swift in Sources */,
D855B3EA27D652C700BCED76 /* TestCoreDataStack.swift in Sources */,
D8AE48C12C57B1550092A2A6 /* SentryLevelTests.swift in Sources */,
63FE721820DA66EC00CDBAE8 /* TestThread.m in Sources */,
Expand Down
35 changes: 35 additions & 0 deletions Sources/Sentry/SentryFileIOTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,30 @@ - (BOOL)measureNSData:(NSData *)data
return result;
}

- (BOOL)measureNSData:(NSData *)data
writeToURL:(NSURL *)url
options:(NSDataWritingOptions)writeOptionsMask
origin:(NSString *)origin
error:(NSError **)error
method:(BOOL (^)(NSURL *, NSDataWritingOptions, NSError **))method
{
// We dont track reads from a url that is not a file url
// because these reads are handled by NSURLSession and
// SentryNetworkTracker will create spans in these cases.
if (![url.scheme isEqualToString:NSURLFileScheme])
return method(url, writeOptionsMask, error);

id<SentrySpan> span = [self startTrackingWritingNSData:data filePath:[url path] origin:origin];

BOOL result = method(url, writeOptionsMask, error);

if (span != nil) {
[self finishTrackingNSData:data span:span];
}

return result;
}

- (NSData *)measureNSDataFromFile:(NSString *)path
origin:(NSString *)origin
method:(NSData * (^)(NSString *))method
Expand Down Expand Up @@ -178,6 +202,13 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path
return result;
}

- (nullable id<SentrySpan>)spanForPath:(NSString *)path
origin:(NSString *)origin
operation:(NSString *)operation
{
return [self spanForPath:path origin:origin operation:operation size:0];
}

- (nullable id<SentrySpan>)spanForPath:(NSString *)path
origin:(NSString *)origin
operation:(NSString *)operation
Expand All @@ -199,6 +230,10 @@ - (BOOL)measureNSFileManagerCreateFileAtPath:(NSString *)path
description:[self transactionDescriptionForFile:path
fileSize:size]];
ioSpan.origin = origin;
if (size > 0) {
[ioSpan setDataValue:[NSNumber numberWithUnsignedInteger:size]
forKey:SentrySpanDataKey.fileSize];
}
}];

if (ioSpan == nil) {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Sentry/SentryFileIOTrackingIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ - (BOOL)installWithOptions:(SentryOptions *)options

- (SentryIntegrationOption)integrationOptions
{
return kIntegrationOptionEnableSwizzling | kIntegrationOptionIsTracingEnabled
| kIntegrationOptionEnableAutoPerformanceTracing | kIntegrationOptionEnableFileIOTracing;
return kIntegrationOptionIsTracingEnabled | kIntegrationOptionEnableAutoPerformanceTracing
| kIntegrationOptionEnableFileIOTracing;
}

- (void)uninstall
Expand Down
20 changes: 20 additions & 0 deletions Sources/Sentry/include/SentryFileIOTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ SENTRY_NO_INIT
error:(NSError **)error
method:(BOOL (^)(NSString *, NSDataWritingOptions, NSError **))method;

/**
* Measure NSData 'writeToFile:options:error:' method.
*/
- (BOOL)measureNSData:(NSData *)data
writeToURL:(NSURL *)url
options:(NSDataWritingOptions)writeOptionsMask
origin:(NSString *)origin
error:(NSError **)error
method:(BOOL (^)(NSURL *, NSDataWritingOptions, NSError **))method;

/**
* Measure NSData 'initWithContentsOfFile:' method.
*/
Expand Down Expand Up @@ -80,6 +90,16 @@ SENTRY_NO_INIT
method:(BOOL (^)(NSString *, NSData *,
NSDictionary<NSFileAttributeKey, id> *))method;

// MARK: - Internal Methods available for Swift Extension

- (nullable id<SentrySpan>)spanForPath:(NSString *)path
origin:(NSString *)origin
operation:(NSString *)operation;
- (nullable id<SentrySpan>)spanForPath:(NSString *)path
origin:(NSString *)origin
operation:(NSString *)operation
size:(NSUInteger)size;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
@_implementationOnly import _SentryPrivate

/// A ``Data`` extension that tracks read and write operations with Sentry.
///
/// - Note: Methods provided by this extension reflect the same functionality as the original ``Data`` methods, but they track the operation with Sentry.
public extension Data {

// MARK: - Reading Data from a File

/// Creates a data object from the data at the specified file URL, tracking the operation with Sentry.
///
/// - Important: Using this method with auto-instrumentation for file operations enabled can lead to duplicate spans on older operating system versions.
/// It is recommended to use either automatic or manual instrumentation. You can disable automatic instrumentation by setting
/// `options.enableSwizzling` to `false` when initializing Sentry.
/// - Parameters:
/// - url: The location on disk of the data to read.
/// - options: The mask specifying the options to use when reading the data. For more information, see ``NSData.ReadingOptions``.
/// - Note: See ``Data.init(contentsOf:options:)`` for more information.
init(contentsOfUrlWithSentryTracing url: URL, options: Data.ReadingOptions = []) throws {
let tracker = SentryFileIOTracker.sharedInstance()
self = try tracker
.measureReadingData(
from: url,
options: options,
origin: SentryTraceOrigin.manualFileData) { url, options in
try Data(contentsOf: url, options: options)
}
}

// MARK: - Writing Data to a File

/// Write the contents of the `Data` to a location, tracking the operation with Sentry.
///
/// - Important: Using this method with auto-instrumentation for file operations enabled can lead to duplicate spans on older operating system versions.
/// It is recommended to use either automatic or manual instrumentation. You can disable automatic instrumentation by setting
/// `options.enableSwizzling` to `false` when initializing Sentry.
/// - Parameters:
/// - url: The location to write the data into.
/// - options: Options for writing the data. Default value is `[]`.
/// - Note: See ``Data.write(to:options:)`` for more information.
func writeWithSentryTracing(to url: URL, options: Data.WritingOptions = []) throws {
let tracker = SentryFileIOTracker.sharedInstance()
try tracker
.measureWritingData(
self,
to: url,
options: options,
origin: SentryTraceOrigin.manualFileData) { data, url, options in
try data.write(to: url, options: options)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@_implementationOnly import _SentryPrivate

extension SentryFileIOTracker {
func measureReadingData(
from url: URL,
options: Data.ReadingOptions,
origin: String,
method: (_ url: URL, _ options: Data.ReadingOptions) throws -> Data
) rethrows -> Data {
// We dont track reads from a url that is not a file url
// because these reads are handled by NSURLSession and
// SentryNetworkTracker will create spans in these cases.
guard url.scheme == NSURLFileScheme else {
return try method(url, options)
}
guard let span = self.span(forPath: url.path, origin: origin, operation: SentrySpanOperation.fileRead) else {
return try method(url, options)
}
defer {
span.finish()
}
let data = try method(url, options)
span.setData(value: data.count, key: SentrySpanDataKey.fileSize)
return data
}

func measureWritingData(
_ data: Data,
to url: URL,
options: Data.WritingOptions,
origin: String,
method: (_ data: Data, _ url: URL, _ options: Data.WritingOptions) throws -> Void
) rethrows {
// We dont track reads from a url that is not a file url
// because these reads are handled by NSURLSession and
// SentryNetworkTracker will create spans in these cases.
guard url.scheme == NSURLFileScheme else {
return try method(data, url, options)
}
guard let span = self.span(forPath: url.path, origin: origin, operation: SentrySpanOperation.fileWrite, size: UInt(data.count)) else {
return try method(data, url, options)
}
defer {
span.finish()
}
try method(data, url, options)
}
}
Loading
Loading