Skip to content
Merged
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
94 changes: 70 additions & 24 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion SentryTestUtils/Sources/TestClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class TestClient: SentryClientInternal {
public override init?(options: NSObject) {
super.init(
options: options,
dateProvider: TestCurrentDateProvider(),
transportAdapter: TestTransportAdapter(transports: [TestTransport()], options: options as! Options),
fileManager: try! TestFileManager(
options: options as? Options,
Expand All @@ -23,9 +24,20 @@ public class TestClient: SentryClientInternal {

// Without this override we get a fatal error: use of unimplemented initializer
// see https://stackoverflow.com/questions/28187261/ios-swift-fatal-error-use-of-unimplemented-initializer-init
@_spi(Private) public override init(options: NSObject, transportAdapter: SentryTransportAdapter, fileManager: SentryFileManager, threadInspector: SentryDefaultThreadInspector, debugImageProvider: SentryDebugImageProvider, random: SentryRandomProtocol, locale: Locale, timezone: TimeZone) {
@_spi(Private) public override init(
options: NSObject,
dateProvider: SentryCurrentDateProvider,
transportAdapter: SentryTransportAdapter,
fileManager: SentryFileManager,
threadInspector: SentryDefaultThreadInspector,
debugImageProvider: SentryDebugImageProvider,
random: SentryRandomProtocol,
locale: Locale,
timezone: TimeZone
) {
super.init(
options: options,
dateProvider: dateProvider,
transportAdapter: transportAdapter,
fileManager: fileManager,
threadInspector: threadInspector,
Expand Down
17 changes: 15 additions & 2 deletions SentryTestUtils/Sources/TestCurrentDateProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Foundation
public var driftTimeInterval = 0.1

private var _systemUptime: TimeInterval = 0
private var _absoluteTime: UInt64 = 0

// NSLock isn't reentrant, so we use NSRecursiveLock.
private let lock = NSRecursiveLock()
Expand All @@ -36,6 +37,7 @@ import Foundation
lock.synchronized {
setDate(date: TestCurrentDateProvider.defaultStartingDate)
internalSystemTime = 0
_absoluteTime = 0
}
}

Expand All @@ -49,21 +51,26 @@ import Foundation
public func advance(by seconds: TimeInterval) {
lock.synchronized {
setDate(date: date().addingTimeInterval(seconds))
internalSystemTime += seconds.toNanoSeconds()
let nanoseconds = seconds.toNanoSeconds()
internalSystemTime += nanoseconds
_absoluteTime += nanoseconds
}
}

public func advanceBy(nanoseconds: UInt64) {
lock.synchronized {
setDate(date: date().addingTimeInterval(nanoseconds.toTimeInterval()))
internalSystemTime += nanoseconds
_absoluteTime += nanoseconds
}
}

public func advanceBy(interval: TimeInterval) {
lock.synchronized {
setDate(date: date().addingTimeInterval(interval))
internalSystemTime += interval.toNanoSeconds()
let nanoseconds = interval.toNanoSeconds()
internalSystemTime += nanoseconds
_absoluteTime += nanoseconds
}
}

Expand Down Expand Up @@ -101,4 +108,10 @@ import Foundation
return _timezoneOffsetValue
}
}

public func getAbsoluteTime() -> UInt64 {
return lock.synchronized {
return _absoluteTime
}
}
}
7 changes: 7 additions & 0 deletions Sources/Sentry/Public/SentryDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,20 @@
-(instancetype)init NS_UNAVAILABLE; \
+(instancetype) new NS_UNAVAILABLE;

@class SentryAttribute;
@class SentryBreadcrumb;
@class SentryEvent;
@class SentrySamplingContext;
@class SentryUserFeedbackConfiguration;
@class SentryLog;
@protocol SentrySpan;

// Compatibility alias to maintain backward compatibility with existing Objective-C code.
// SentryLogAttribute is an alias for SentryAttribute, allowing code like
// [[SentryLogAttribute alloc] initWithString:...] to continue working, after `SentryLog.Attribute`
// was renamed to `SentryAttribute`.
@compatibility_alias SentryLogAttribute SentryAttribute;

/**
* Block used for returning after a request finished
*/
Expand Down
6 changes: 5 additions & 1 deletion Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ - (_Nullable instancetype)initWithOptions:(SentryOptions *)options
[[SentryDefaultThreadInspector alloc] initWithOptions:options];

return [self initWithOptions:options
dateProvider:SentryDependencyContainer.sharedInstance.dateProvider
transportAdapter:transportAdapter
fileManager:fileManager
threadInspector:threadInspector
Expand All @@ -95,6 +96,7 @@ - (_Nullable instancetype)initWithOptions:(SentryOptions *)options
}

- (instancetype)initWithOptions:(SentryOptions *)options
dateProvider:(id<SentryCurrentDateProvider>)dateProvider
transportAdapter:(SentryTransportAdapter *)transportAdapter
fileManager:(SentryFileManager *)fileManager
threadInspector:(SentryDefaultThreadInspector *)threadInspector
Expand All @@ -115,7 +117,9 @@ - (instancetype)initWithOptions:(SentryOptions *)options
self.timezone = timezone;
self.attachmentProcessors = [[NSMutableArray alloc] init];

self.logBatcher = [[SentryLogBatcher alloc] initWithOptions:options delegate:self];
self.logBatcher = [[SentryLogBatcher alloc] initWithOptions:options
dateProvider:dateProvider
delegate:self];

// The SDK stores the installationID in a file. The first call requires file IO. To avoid
// executing this on the main thread, we cache the installationID async here.
Expand Down
11 changes: 11 additions & 0 deletions Sources/Swift/Core/Helper/SentryCurrentDateProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import Foundation
func timezoneOffset() -> Int
func systemTime() -> UInt64
func systemUptime() -> TimeInterval
func getAbsoluteTime() -> UInt64
}

extension SentryCurrentDateProvider {
public func getAbsoluteTime() -> UInt64 {
clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
}
}

@objcMembers
Expand All @@ -34,6 +41,10 @@ import Foundation
ProcessInfo.processInfo.systemUptime
}

public func getAbsoluteTime() -> UInt64 {
SentryDefaultCurrentDateProvider.getAbsoluteTime()
}

public static func getAbsoluteTime() -> UInt64 {
clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/Swift/Helper/SentryDispatchQueueWrapper.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
@_implementationOnly import _SentryPrivate

protocol SentryDispatchQueueWrapperProtocol {
func dispatchSync(_ block: @escaping () -> Void)
func dispatchAsync(_ block: @escaping () -> Void)
func dispatch(after interval: TimeInterval, block: @escaping () -> Void)
func dispatchAsyncOnMainQueueIfNotMainThread(block: @escaping () -> Void)
func dispatchSyncOnMainQueue(block: @escaping () -> Void)
func dispatchSyncOnMainQueue(_ block: @escaping () -> Void, timeout: Double)
func dispatchOnce(_ predicate: UnsafeMutablePointer<CLong>, block: @escaping () -> Void)
func dispatch(after interval: TimeInterval, workItem: DispatchWorkItem)
}

// This is the Swift version of `_SentryDispatchQueueWrapperInternal`
// It exists to allow the implementation of `_SentryDispatchQueueWrapperInternal`
// to be accessible to Swift without making that header file public
Expand Down Expand Up @@ -76,3 +87,5 @@
return true
}
}

extension SentryDispatchQueueWrapper: SentryDispatchQueueWrapperProtocol {}
106 changes: 106 additions & 0 deletions Sources/Swift/Protocol/SentryAttribute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/// A typed attribute that can be attached to structured item entries used by Logs
///
/// `Attribute` provides a type-safe way to store structured data alongside item messages.
/// Supports String, Bool, Int, and Double types.
@objcMembers
public final class SentryAttribute: NSObject {
/// The type identifier for this attribute ("string", "boolean", "integer", "double")
public let type: String
/// The actual value stored in this attribute
public let value: Any

public init(string value: String) {
self.type = "string"
self.value = value
super.init()
}

public init(boolean value: Bool) {
self.type = "boolean"
self.value = value
super.init()
}

public init(integer value: Int) {
self.type = "integer"
self.value = value
super.init()
}

public init(double value: Double) {
self.type = "double"
self.value = value
super.init()
}

/// Creates a double attribute from a float value
public init(float value: Float) {
self.type = "double"
self.value = Double(value)
super.init()
}

internal init(value: Any) {
switch value {
case let stringValue as String:
self.type = "string"
self.value = stringValue
case let boolValue as Bool:
self.type = "boolean"
self.value = boolValue
case let intValue as Int:
self.type = "integer"
self.value = intValue
case let doubleValue as Double:
self.type = "double"
self.value = doubleValue
case let floatValue as Float:
self.type = "double"
self.value = Double(floatValue)
default:
// For any other type, convert to string representation
self.type = "string"
self.value = String(describing: value)
}
super.init()
}
}

// MARK: - Internal Encodable Support
@_spi(Private) extension SentryAttribute: Encodable {
private enum CodingKeys: String, CodingKey {
case value
case type
}

@_spi(Private) public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(type, forKey: .type)

switch type {
case "string":
guard let stringValue = value as? String else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected String but got \(Swift.type(of: value))"))
}
try container.encode(stringValue, forKey: .value)
case "boolean":
guard let boolValue = value as? Bool else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected Bool but got \(Swift.type(of: value))"))
}
try container.encode(boolValue, forKey: .value)
case "integer":
guard let intValue = value as? Int else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected Int but got \(Swift.type(of: value))"))
}
try container.encode(intValue, forKey: .value)
case "double":
guard let doubleValue = value as? Double else {
throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected Double but got \(Swift.type(of: value))"))
}
try container.encode(doubleValue, forKey: .value)
default:
try container.encode(String(describing: value), forKey: .value)
}
}
}
3 changes: 3 additions & 0 deletions Sources/Swift/Protocol/SentryLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
@objc
@objcMembers
public final class SentryLog: NSObject {
/// Alias for `SentryAttribute` to maintain backward compatibility after `SentryLog.Attribute` was renamed to `SentryAttribute`.
public typealias Attribute = SentryAttribute

/// The timestamp when the log event occurred
public var timestamp: Date
/// The trace ID to associate this log with distributed tracing. This will be set to a valid non-empty value during processing.
Expand Down
Loading
Loading