Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions PostHog.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ Pod::Spec.new do |s|
'vendor/libwebp/**/*.{h,c}'
]
s.resource_bundles = { "PostHog" => "PostHog/Resources/PrivacyInfo.xcprivacy" }

# Include the upload script for dSYM uploads
s.preserve_paths = 'build-tools/upload-symbols'
end
76 changes: 76 additions & 0 deletions PostHog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions PostHog/Error Tracking/Models/PostHogBinaryImageInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// PostHogBinaryImageInfo.swift
// PostHog
//
// Created by Ioannis Josephides on 27/11/2025.
//

import Foundation

/// Information about a loaded binary image (executable or dynamic library)
///
/// This struct contains the metadata needed for server-side symbolication:
/// - UUID for matching uploaded dSYM files
/// - Load addresses for calculating symbol offsets
/// - Size for determining address ranges
struct PostHogBinaryImageInfo {
/// Full path to the binary image (e.g., "/usr/lib/system/libsystem_kernel.dylib")
let name: String

/// UUID of the binary image, used for dSYM matching
/// Format: "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
let uuid: String?

/// Virtual memory address from the Mach-O headers (preferred load address)
let vmAddress: UInt64

/// Actual load address in memory (may differ from vmAddress due to ASLR)
let address: UInt64

/// Size of the binary image in bytes
let size: UInt64

var toDictionary: [String: Any] {
var dict: [String: Any] = [
"type": "macho",
"code_file": name,
"image_addr": String(format: "0x%llx", address),
"image_size": size,
]

if let uuid = uuid {
dict["debug_id"] = uuid
}

if vmAddress > 0 {
dict["image_vmaddr"] = String(format: "0x%llx", vmAddress)
}

return dict
}
}
62 changes: 62 additions & 0 deletions PostHog/Error Tracking/Models/PostHogStackFrame.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// PostHogStackFrame.swift
// PostHog
//
// Created by Ioannis Josephides on 02/12/2025.
//

import Foundation

/// Information about a single stack frame
struct PostHogStackFrame {
/// The instruction address where the frame was executing
let instructionAddress: UInt64

/// Name of the binary module (e.g., "MyApp", "Foundation")
let module: String?

/// Corresponding package
let package: String?

/// Load address of the binary image in memory
let imageAddress: UInt64?

/// Whether this frame is considered part of the application code
let inApp: Bool

/// Function or symbol name (raw symbol without demangling)
let function: String?

/// Address of the symbol/function
let symbolAddress: UInt64?

var toDictionary: [String: Any] {
var dict: [String: Any] = [:]

dict["instruction_addr"] = String(format: "0x%llx", instructionAddress)
dict["platform"] = "ios" // always the same for iOS SDK (may need to revisit)
dict["in_app"] = inApp

if let module = module {
dict["module"] = module
}

if let package = package {
dict["package"] = package
}

if let imageAddress = imageAddress {
dict["image_addr"] = String(format: "0x%llx", imageAddress)
}

if let function = function {
dict["function"] = function
}

if let symbolAddress = symbolAddress {
dict["symbol_addr"] = String(format: "0x%llx", symbolAddress)
}

return dict
}
}
85 changes: 85 additions & 0 deletions PostHog/Error Tracking/PostHogErrorTrackingConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// PostHogErrorTrackingConfig.swift
// PostHog
//
// Created by Ioannis Josephides on 11/11/2025.
//

import Foundation

/// Configuration for error tracking and exception capture
///
/// This class controls how exceptions are captured and processed,
/// including which stack trace frames are marked as "in-app" code.
@objc public class PostHogErrorTrackingConfig: NSObject {
// MARK: - In-App Detection Configuration

/// List of package/bundle identifiers to be considered in-app frames
///
/// Takes precedence over `inAppExcludes`.
/// If a frame's module matches any prefix in this list,
/// it will be marked as in-app.
///
/// Example:
/// ```swift
/// config.errorTrackingConfig.inAppIncludes = [
/// "com.mycompany.MyApp",
/// "com.mycompany.SharedUtils"
/// ]
/// ```
///
/// **Default behavior:**
/// - Automatically includes main bundle identifier
/// - Automatically includes executable name
///
/// **Precedence:** Priority 1 (highest)
@objc public var inAppIncludes: [String] = []

/// List of package/bundle identifiers to be excluded from in-app frames
///
/// Frames matching these prefixes will be marked as not in-app,
/// unless they also match `inAppIncludes` (which takes precedence).
///
/// Example:
/// ```swift
/// config.errorTrackingConfig.inAppExcludes = [
/// "ThirdPartySDK",
/// "AnalyticsLib"
/// ]
/// ```
///
/// **Precedence:** Priority 2 (after inAppIncludes)
@objc public var inAppExcludes: [String] = []

/// Configures whether stack trace frames are considered in-app by default
/// when the origin cannot be determined or no explicit includes/excludes match.
///
/// - If `true` (default): Frames are in-app unless explicitly excluded (allowlist approach)
/// - If `false`: Frames are external unless explicitly included (denylist approach)
///
/// **Default behavior when true:**
/// - Known system frameworks (Foundation, UIKit, etc.) are excluded
/// - All other packages are in-app unless in `inAppExcludes`
///
/// **Precedence:** Priority 4 (final fallback)
///
/// Default: true
@objc public var inAppByDefault: Bool = true

// MARK: - Initialization

override public init() {
super.init()

// Auto-add main bundle identifier
if let bundleId = Bundle.main.bundleIdentifier {
inAppIncludes.append(bundleId)
}

// Auto-add executable name
// This helps catch app code when bundle ID might not be in module name
if let executableName = Bundle.main.object(forInfoDictionaryKey: "CFBundleExecutable") as? String {
inAppIncludes.append(executableName)
}
}
}
Loading