Skip to content

Commit

Permalink
Add base Sentry client, and stack trace parser
Browse files Browse the repository at this point in the history
  • Loading branch information
mats-stripe committed Oct 23, 2024
1 parent 2de3f87 commit 96a02d7
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 4 deletions.
4 changes: 2 additions & 2 deletions StripeCore/StripeCore/Source/Helpers/InstallMethod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

import Foundation

enum InstallMethod: String {
@_spi(STP) public enum InstallMethod: String {
case cocoapods = "C"
case spm = "S"
case binary = "B" // Built via export_builds.sh
case xcode = "X" // Directly built via Xcode or xcodebuild

static let current: InstallMethod = {
@_spi(STP) public static let current: InstallMethod = {
#if COCOAPODS
return .cocoapods
#elseif SWIFT_PACKAGE
Expand Down
4 changes: 2 additions & 2 deletions StripeCore/StripeCore/Source/Helpers/STPDeviceUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import Foundation

struct STPDeviceUtils {
static var deviceType: String? {
@_spi(STP) public struct STPDeviceUtils {
@_spi(STP) public static var deviceType: String? {
var systemInfo: utsname = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
492039952CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */; };
492651662C24C9E7001DDBCA /* TestModeAutofillBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */; };
492651682C25C0C2001DDBCA /* info@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 492651672C25C0C2001DDBCA /* info@3x.png */; };
494D32162CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32152CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift */; };
494D32182CC819F000C5EFAF /* SentryContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32172CC819F000C5EFAF /* SentryContext.swift */; };
494D321A2CC8237F00C5EFAF /* SentryPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32192CC8237F00C5EFAF /* SentryPayload.swift */; };
494D321C2CC8264D00C5EFAF /* SentryStacktrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D321B2CC8264D00C5EFAF /* SentryStacktrace.swift */; };
494D322A2CC85E9000C5EFAF /* TraceSymbolsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32292CC85E9000C5EFAF /* TraceSymbolsParser.swift */; };
494D322C2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D322B2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift */; };
494D62072C45B9B700106519 /* link_logo@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 494D62062C45B9B700106519 /* link_logo@3x.png */; };
495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */; };
496A6AE72C29E0BB00D34F8E /* testmode@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 496A6AE62C29E0BB00D34F8E /* testmode@3x.png */; };
Expand Down Expand Up @@ -322,6 +328,12 @@
492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsWebFlowTests.swift; sourceTree = "<group>"; };
492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestModeAutofillBannerView.swift; sourceTree = "<group>"; };
492651672C25C0C2001DDBCA /* info@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "info@3x.png"; sourceTree = "<group>"; };
494D32152CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSentryClient.swift; sourceTree = "<group>"; };
494D32172CC819F000C5EFAF /* SentryContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryContext.swift; sourceTree = "<group>"; };
494D32192CC8237F00C5EFAF /* SentryPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryPayload.swift; sourceTree = "<group>"; };
494D321B2CC8264D00C5EFAF /* SentryStacktrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStacktrace.swift; sourceTree = "<group>"; };
494D32292CC85E9000C5EFAF /* TraceSymbolsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceSymbolsParser.swift; sourceTree = "<group>"; };
494D322B2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceSymbolsParserTests.swift; sourceTree = "<group>"; };
494D62062C45B9B700106519 /* link_logo@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "link_logo@3x.png"; sourceTree = "<group>"; };
495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsTheme.swift; sourceTree = "<group>"; };
496A6AE62C29E0BB00D34F8E /* testmode@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testmode@3x.png"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -636,6 +648,7 @@
328390D72E3911449BB9FD0B /* Analytics */ = {
isa = PBXGroup;
children = (
494D32132CC7FCCF00C5EFAF /* Sentry */,
DBBF5CEE2C9030B2D374BC76 /* FinancialConnectionsAnalyticsClient.swift */,
A6038978C79785C18257CD74 /* FinancialConnectionsSheetAnalytics.swift */,
);
Expand Down Expand Up @@ -671,6 +684,18 @@
path = FinancialConnectionsSDK;
sourceTree = "<group>";
};
494D32132CC7FCCF00C5EFAF /* Sentry */ = {
isa = PBXGroup;
children = (
494D32152CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift */,
494D32172CC819F000C5EFAF /* SentryContext.swift */,
494D32192CC8237F00C5EFAF /* SentryPayload.swift */,
494D321B2CC8264D00C5EFAF /* SentryStacktrace.swift */,
494D32292CC85E9000C5EFAF /* TraceSymbolsParser.swift */,
);
path = Sentry;
sourceTree = "<group>";
};
49C911362C597EAF00589E0D /* LinkLogin */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1083,6 +1108,7 @@
CF731140836AE438C7F4F6AB /* StringExtensionsTests.swift */,
497142BB2C514B08000DFA64 /* FlowRouterTests.swift */,
492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */,
494D322B2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift */,
);
path = StripeFinancialConnectionsTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1230,6 +1256,8 @@
"zh-Hant",
);
mainGroup = 7879CBA341D7E807714A831B;
packageReferences = (
);
productRefGroup = 820BF9CF057CF92872BC3C15 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -1299,6 +1327,7 @@
6A3DA1F52C34A37F005C3F6E /* GenericInfoViewController.swift in Sources */,
FBF513C7F73002FA30CC7C21 /* ConsumerSessionModels.swift in Sources */,
6A3739142C40558900D1F765 /* GenericInfoBodyView.swift in Sources */,
494D321A2CC8237F00C5EFAF /* SentryPayload.swift in Sources */,
EC74B719F0FA1A977EF4708C /* FinancialConnectionsAccount.swift in Sources */,
460C7685096AA6C693309647 /* FinancialConnectionsAuthSession.swift in Sources */,
AB5AFAC3C70D6195075DE5AE /* FinancialConnectionsBulletPoint.swift in Sources */,
Expand All @@ -1308,6 +1337,7 @@
F67624595BD2CD7B6793BFDA /* FinancialConnectionsImage.swift in Sources */,
07712610C7D2F484AAB96982 /* FinancialConnectionsInstitution.swift in Sources */,
7386E1F9256B23CE29BF996D /* FinancialConnectionsInstitutionSearchResultResource.swift in Sources */,
494D321C2CC8264D00C5EFAF /* SentryStacktrace.swift in Sources */,
C7D2763ACCE2CC71E788E18F /* FinancialConnectionsLegalDetailsNotice.swift in Sources */,
B271AAF41C9FE6AE392B88D3 /* FinancialConnectionsMixedOAuthParams.swift in Sources */,
DAA51ABB496551074DBA1A20 /* FinancialConnectionsNetworkedAccountsResponse.swift in Sources */,
Expand Down Expand Up @@ -1374,6 +1404,7 @@
3446145FCA3278D51A9D4B80 /* AttachLinkedPaymentAccountDataSource.swift in Sources */,
E3F62D2F9C344A1178030E8E /* AttachLinkedPaymentAccountViewController.swift in Sources */,
707C265C4179A8FEC98913FE /* ConsentBodyView.swift in Sources */,
494D322A2CC85E9000C5EFAF /* TraceSymbolsParser.swift in Sources */,
465AE8A58AD2183E1E2042FE /* ConsentDataSource.swift in Sources */,
97C528CE821C6A55D58F68A4 /* ConsentFooterView.swift in Sources */,
8927328EE28A0C94B5AB69DB /* ConsentLogoView.swift in Sources */,
Expand Down Expand Up @@ -1431,6 +1462,7 @@
9AF6EC34D666BEB3C1397092 /* BulletPointLabelView.swift in Sources */,
313F5F7F2B0BE5D100BD98A9 /* Docs.docc in Sources */,
F65E8D16DE691EB6C99C4521 /* Button+Extensions.swift in Sources */,
494D32162CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift in Sources */,
33FA1684CE79F21271D14F23 /* HitTestStackView.swift in Sources */,
691619AE9A989548ABA36535 /* HitTestView.swift in Sources */,
91A3583A0BDE0F8F0C4AD3E2 /* InstitutionIconView.swift in Sources */,
Expand All @@ -1440,6 +1472,7 @@
49C911372C597EAF00589E0D /* LinkLoginDataSource.swift in Sources */,
6A7814182B361C5000168992 /* PaneLayoutView+Extensions.swift in Sources */,
99F41681B77ECB0090F34E31 /* SFSafariViewController+Extensions.swift in Sources */,
494D32182CC819F000C5EFAF /* SentryContext.swift in Sources */,
AA80602323C28AFAC391358D /* TimeInterval+Extensions.swift in Sources */,
E637387728FA1597B1B51E5D /* UIImage+Extensions.swift in Sources */,
495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */,
Expand Down Expand Up @@ -1471,6 +1504,7 @@
492039952CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift in Sources */,
700B745FEF43088D9E34C0E4 /* AccountPickerHelpersTests.swift in Sources */,
6744CB1B182C5F7220B0B804 /* AuthFlowHelpersTests.swift in Sources */,
494D322C2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift in Sources */,
39E5D4531961150E9CB3262F /* EmptyFinancialConnectionsAPIClient.swift in Sources */,
CB734C25A19D38A87876FB2B /* FinancialConnectionsAnalyticsTest.swift in Sources */,
497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// FinancialConnectionsSentryClient.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation
@_spi(STP) import StripeCore

protocol FinancialConnectionsErrorReporter {
func report(error: Error, parameters: [String: Any])
}

class FinancialConnectionsSentryClient: FinancialConnectionsErrorReporter {
private static let endpoint: URL = {
let projectId = "871"
var components = URLComponents()
components.scheme = "https"
components.host = "errors.stripe.com"
components.path = "/api/\(projectId)/envelope/"
return components.url!
}()

func report(error: Error, parameters: [String: Any]) {
// TODO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// SentryContext.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation
@_spi(STP) import StripeCore
import UIKit

struct SentryContext: Encodable {
let app: SentryAppContext
let os: SentryOsContext
let device: SentryDeviceContext

static let shared: SentryContext = {
let app = SentryAppContext(
appIdentifier: Bundle.stp_applicationBundleId() ?? "",
appName: Bundle.stp_applicationName() ?? "",
appVersion: Bundle.stp_applicationVersion() ?? ""
)
let os = SentryOsContext(
name: "iOS",
version: UIDevice.current.systemVersion,
type: InstallMethod.current.rawValue
)
let device = SentryDeviceContext(
modelId: UIDevice.current.identifierForVendor?.uuidString ?? "",
model: UIDevice.current.model,
manufacturer: "Apple",
type: STPDeviceUtils.deviceType ?? ""
)

return SentryContext(app: app, os: os, device: device)
}()
}

struct SentryAppContext: Encodable {
let appIdentifier: String
let appName: String
let appVersion: String
}

struct SentryOsContext: Encodable {
let name: String
let version: String
let type: String
}

struct SentryDeviceContext: Encodable {
let modelId: String
let model: String
let manufacturer: String
let type: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// SentryPayload.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation
@_spi(STP) import StripeCore

struct SentryPayload: Encodable {
let eventId: String = UUID().uuidString.replacingOccurrences(of: "-", with: "")
let timestamp: TimeInterval = Date().timeIntervalSince1970
let release: String = StripeAPIConfiguration.STPSDKVersion
let context: SentryContext = .shared
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// SentryStacktrace.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation

protocol SentryTrace: Encodable, Equatable {
var function: String { get }
}

struct RootTrace: SentryTrace {
let file: String
let function: String
let lineno: Int
}

struct CallStacktrace: SentryTrace {
let module: String
let function: String
}

enum SentryStacktrace {
static func capture(
filePath: String = #file,
function: String = #function,
line: Int = #line,
callsiteDepth: Int = 1
) -> [any SentryTrace] {
var traces: [any SentryTrace] = []
if let file = filePath.components(separatedBy: "/").last {
// Start with a Root trace, which includes the file, function, and lineno.
traces.append(RootTrace(
file: file,
function: function,
lineno: line
))
}

// Add all other traces by adding 1 to the callsite depth. This removes the meta call
// to `SentryStacktrace.capture` from the stacktrace.
let callStackTrace = TraceSymbolsParser.current(callsiteDepth: callsiteDepth + 1)
traces.append(contentsOf: callStackTrace)
return traces
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// TraceSymbolsParser.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation

enum TraceSymbolsParser {
/// Get the current call stack as an `CallStacktrace` array representaion.
/// Removes the first `callsiteDepth` traces from the stack.
/// These traces are usually meta traces from classes calling the parser.
/// i.e. removes `TraceSymbolsParser.current` from the stack trace
static func current(callsiteDepth: Int = 0) -> [CallStacktrace] {
let callStackSymbols: [String] = Array(Thread.callStackSymbols.dropFirst(callsiteDepth))
return callStackSymbols.compactMap { symbols in
Self.parse(symbols: symbols)
}
}

/// Parses a line of `Thread.callStackSymbols` to `CallStackTrace`.
/// - `symbols`: Input which follows the `DLADDR` format.
/// ```
/// // {depth} {fname} {fbase} {sname} + {saddr}
/// (number with radix 10) (string) (number with radix 16) (string) + (number with radix 10)
/// ```
/// This extracts `fname` into the `module`, and `sname` into the `functions.`
static func parse(symbols: String) -> CallStacktrace? {
// Split the input string by whitespaces and filter out empty components
let components = symbols.split(whereSeparator: \.isWhitespace).filter { !$0.isEmpty }
guard components.indices.contains(3) else {
return nil // Invalid symbol, not enough components.
}
// The `module` is the second component
let module = String(components[1])

// The `function` is everything from the fourth component up to but not including the "+"
let functionComponents = components[3...]
let functionString = functionComponents.joined(separator: " ")

// Find the index of the "+" symbol in the original string
guard let plusIndex = functionString.range(of: "+")?.lowerBound else {
return nil // "+" symbol not found.
}

// Extract the final function string from the joined components, trimmed and until the "+"
let functionEndIndex = symbols.distance(from: symbols.startIndex, to: plusIndex)
let function = String(functionString.prefix(functionEndIndex)).trimmingCharacters(in: .whitespaces)
return CallStacktrace(module: module, function: function)
}
}
Loading

0 comments on commit 96a02d7

Please sign in to comment.