diff --git a/Changelog.md b/Changelog.md index 438de13..b98ec85 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ - Add more exceptionType processing to crash beacon - Display crash terminationReason as meta data rather than error message - Add Objective-C target ObjectiveCAppExample to InstanaAgentExample +- Support hybrid agent id and version (if invoked by flutter-agent/react-native-agent) ## 1.6.7 - Add more raw crash payload info to stackTrace diff --git a/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon.swift b/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon.swift index b05ccef..9189877 100644 --- a/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon.swift +++ b/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon.swift @@ -422,6 +422,17 @@ struct CoreBeacon: Codable { func isCrashPayloadField(fieldKey: String) -> Bool { return fieldKey == "st" || fieldKey == "ast" } + + // If invoked by flutter-agent(f) or react-native-agent(r), + // put calling agent's id and version after iOSAgent version + static func getInstanaAgentVersion(hybridAgentId: String?, hybridAgentVersion: String?) -> String { + let iOSAgentVersion = InstanaSystemUtils.agentVersion + guard let hybridAgentId = hybridAgentId, !hybridAgentId.isEmpty, + let hybridAgentVersion = hybridAgentVersion, !hybridAgentVersion.isEmpty else { + return iOSAgentVersion + } + return "\(iOSAgentVersion):\(hybridAgentId):\(hybridAgentVersion)" + } } extension CoreBeacon: Hashable { diff --git a/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeaconFactory.swift b/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeaconFactory.swift index 71bd4fc..24121e4 100644 --- a/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeaconFactory.swift +++ b/Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeaconFactory.swift @@ -24,7 +24,9 @@ class CoreBeaconFactory { var cbeacon = CoreBeacon.createDefault(viewName: beacon.viewName, key: conf.key, timestamp: beacon.timestamp, sid: session.id, usi: session.usi, - id: beacon.id, mobileFeatures: mobileFeatures) + id: beacon.id, mobileFeatures: mobileFeatures, + hybridAgentId: conf.hybridAgentId, + hybridAgentVersion: conf.hybridAgentVersion) cbeacon.append(properties) switch beacon { case let item as HTTPBeacon: @@ -185,6 +187,8 @@ extension CoreBeacon { usi: UUID?, id: String, mobileFeatures: String?, + hybridAgentId: String?, + hybridAgentVersion: String?, connection: NetworkUtility.ConnectionType = InstanaSystemUtils.networkUtility.connectionType, ect: NetworkUtility.CellularType? = nil) -> CoreBeacon { @@ -203,7 +207,8 @@ extension CoreBeacon { osn: InstanaSystemUtils.systemName, osv: InstanaSystemUtils.systemVersion, dmo: InstanaSystemUtils.deviceModel, - agv: InstanaSystemUtils.agentVersion, + agv: CoreBeacon.getInstanaAgentVersion(hybridAgentId: hybridAgentId, + hybridAgentVersion: hybridAgentVersion), ro: String(InstanaSystemUtils.isDeviceJailbroken), vw: String(Int(InstanaSystemUtils.screenSize.width)), vh: String(Int(InstanaSystemUtils.screenSize.height)), diff --git a/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift b/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift index 456687d..8c2c4c8 100644 --- a/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift +++ b/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift @@ -73,11 +73,16 @@ class InstanaConfiguration { var preQueueUsageTime: TimeInterval var reporterRateLimits: [ReporterRateLimitConfig] var isValid: Bool { !key.isEmpty && !reportingURL.absoluteString.isEmpty } + // set if iOSAgent is invoked by flutter-agent or react-native-agent + var hybridAgentId: String? + var hybridAgentVersion: String? required init(reportingURL: URL, key: String, httpCaptureConfig: HTTPCaptureConfig, enableCrashReporting: Bool, suspendReporting: Set? = nil, slowSendInterval: Instana.Types.Seconds, - usiRefreshTimeIntervalInHrs: Double) { + usiRefreshTimeIntervalInHrs: Double, + hybridAgentId: String?, + hybridAgentVersion: String?) { self.reportingURL = reportingURL self.key = key self.httpCaptureConfig = httpCaptureConfig @@ -88,6 +93,8 @@ class InstanaConfiguration { self.suspendReporting = suspendReporting ?? SuspendReporting.defaults self.slowSendInterval = slowSendInterval self.usiRefreshTimeIntervalInHrs = usiRefreshTimeIntervalInHrs + self.hybridAgentId = hybridAgentId + self.hybridAgentVersion = hybridAgentVersion reporterSendDebounce = Defaults.reporterSendDebounce reporterSendLowBatteryDebounce = Defaults.reporterSendLowBatteryDebounce maxRetries = Defaults.maxRetries @@ -102,12 +109,16 @@ class InstanaConfiguration { enableCrashReporting: Bool, suspendReporting: Set? = nil, slowSendInterval: Instana.Types.Seconds = 0.0, - usiRefreshTimeIntervalInHrs: Double = defaultUsiRefreshTimeIntervalInHrs) + usiRefreshTimeIntervalInHrs: Double = defaultUsiRefreshTimeIntervalInHrs, + hybridAgentId: String? = nil, + hybridAgentVersion: String? = nil) -> InstanaConfiguration { self.init(reportingURL: reportingURL, key: key, httpCaptureConfig: httpCaptureConfig, enableCrashReporting: enableCrashReporting, suspendReporting: suspendReporting, slowSendInterval: slowSendInterval, - usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs) + usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs, + hybridAgentId: hybridAgentId, + hybridAgentVersion: hybridAgentVersion) } } diff --git a/Sources/InstanaAgent/Instana.swift b/Sources/InstanaAgent/Instana.swift index d70469e..e5eaf2d 100644 --- a/Sources/InstanaAgent/Instana.swift +++ b/Sources/InstanaAgent/Instana.swift @@ -81,6 +81,18 @@ import Foundation /// - Returns: true on success, false on error @objc public static func setup(key: String, reportingURL: URL, options: InstanaSetupOptions?) -> Bool { + return setupInternal(key: key, reportingURL: reportingURL, options: options, hybridOptions: nil) + } + + /// Internal use, configures and sets up the Instana agent. + /// + /// - Parameters: + /// - hybridOptions: hybrid agent configuration options (set if invoked by Instana flutter-agent or react-native-agent) + /// + /// - Returns: true on success, false on error + @objc + public static func setupInternal(key: String, reportingURL: URL, options: InstanaSetupOptions?, + hybridOptions: HybridAgentOptions?) -> Bool { var httpCaptureConfig = HTTPCaptureConfig.automatic var collectionEnabled = true var enableCrashReporting = false @@ -112,12 +124,22 @@ import Foundation usiRefreshTimeIntervalInHrs = options.usiRefreshTimeIntervalInHrs } + + var hybridAgentId: String? + var hybridAgentVersion: String? + if let hybridOptions = hybridOptions { + hybridAgentId = hybridOptions.id + hybridAgentVersion = hybridOptions.version + } + let config = InstanaConfiguration.default(key: key, reportingURL: reportingURL, httpCaptureConfig: httpCaptureConfig, enableCrashReporting: enableCrashReporting, suspendReporting: suspendReporting, slowSendInterval: slowSendInterval, - usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs) + usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs, + hybridAgentId: hybridAgentId, + hybridAgentVersion: hybridAgentVersion) let session = InstanaSession(configuration: config, propertyHandler: InstanaPropertyHandler(), collectionEnabled: collectionEnabled) Instana.current = Instana(session: session) diff --git a/Sources/InstanaAgent/InstanaSetupOptions.swift b/Sources/InstanaAgent/InstanaSetupOptions.swift index 8393816..ae24885 100644 --- a/Sources/InstanaAgent/InstanaSetupOptions.swift +++ b/Sources/InstanaAgent/InstanaSetupOptions.swift @@ -37,3 +37,20 @@ import Foundation self.usiRefreshTimeIntervalInHrs = usiRefreshTimeIntervalInHrs } } + +// Hybrid Agent options for setup +@objc public class HybridAgentOptions: NSObject { + public private(set) var id: String + public private(set) var version: String + + /// - Parameters: + /// - id: flutter-agent or react-native-agent + /// - version: version of flutter-agent or react-native-agent + @objc public + init(id: String, version: String) { + // remove leading and trailing spaces + // truncate if too long + self.id = String(id.trimmingCharacters(in: .whitespaces).prefix(16)) + self.version = String(version.trimmingCharacters(in: .whitespaces).prefix(16)) + } +} diff --git a/Tests/InstanaAgentTests/Beacons/CoreBeacon/CoreBeaconTests.swift b/Tests/InstanaAgentTests/Beacons/CoreBeacon/CoreBeaconTests.swift index aaa63bf..3b1c0c8 100644 --- a/Tests/InstanaAgentTests/Beacons/CoreBeacon/CoreBeaconTests.swift +++ b/Tests/InstanaAgentTests/Beacons/CoreBeacon/CoreBeaconTests.swift @@ -33,7 +33,9 @@ class CoreBeaconTests: InstanaTestCase { sid: sessionID, usi: session.usi, id: beaconID, - mobileFeatures: "c") + mobileFeatures: "c", + hybridAgentId: nil, + hybridAgentVersion: nil) coreBeacon.append(props) wifiCoreBeacon = CoreBeacon.createDefault(viewName: viewName, key: key, @@ -42,6 +44,8 @@ class CoreBeaconTests: InstanaTestCase { usi: session.usi, id: beaconID, mobileFeatures: "c", + hybridAgentId: "f", + hybridAgentVersion: "3.0.6", connection: .wifi, ect: .fiveG) wifiCoreBeacon.append(props) @@ -135,7 +139,9 @@ class CoreBeaconTests: InstanaTestCase { // Given let configUsi = InstanaConfiguration(reportingURL: .random, key: "KEY", httpCaptureConfig: .automatic, enableCrashReporting: false, slowSendInterval: 0.0, - usiRefreshTimeIntervalInHrs: usiTrackingNotAllowed) + usiRefreshTimeIntervalInHrs: usiTrackingNotAllowed, + hybridAgentId: nil, + hybridAgentVersion: nil) let sessionUsi = InstanaSession.mock(configuration: configUsi, sessionID: sessionID, metaData: metaData, @@ -148,6 +154,8 @@ class CoreBeaconTests: InstanaTestCase { usi: sessionUsi.usi, id: beaconID, mobileFeatures: "c", + hybridAgentId: "nil", + hybridAgentVersion: nil, connection: .wifi, ect: .fiveG) beacon.append(props) @@ -282,4 +290,31 @@ class CoreBeaconTests: InstanaTestCase { XCTAssertTrue(sut!.hasSuffix("…")) XCTAssertEqual(CoreBeacon.maxLengthPerField, 16384) } + + func test_getInstanaAgentVersion() { + let sut = CoreBeacon.getInstanaAgentVersion(hybridAgentId: nil, hybridAgentVersion: nil) + let expected = "\(InstanaSystemUtils.agentVersion)" + XCTAssertEqual(sut, expected) + + let sutFlutter = CoreBeacon.getInstanaAgentVersion(hybridAgentId: "f", hybridAgentVersion: "3.0.6") + let expectedFlutter = "\(InstanaSystemUtils.agentVersion):f:3.0.6" + XCTAssertEqual(sutFlutter, expectedFlutter) + + let sutRn = CoreBeacon.getInstanaAgentVersion(hybridAgentId: "r", hybridAgentVersion: "2.0.3") + let expectedRn = "\(InstanaSystemUtils.agentVersion):r:2.0.3" + XCTAssertEqual(sutRn, expectedRn) + + // negative cases + let sutMisConfigVer = CoreBeacon.getInstanaAgentVersion(hybridAgentId: nil, hybridAgentVersion: "misConfigedVersion") + let expectedMisConfigVer = "\(InstanaSystemUtils.agentVersion)" + XCTAssertEqual(sutMisConfigVer, expectedMisConfigVer) + + let sutMisConfigId = CoreBeacon.getInstanaAgentVersion(hybridAgentId: "misConfigedId", hybridAgentVersion: nil) + let expectedMisConfigId = "\(InstanaSystemUtils.agentVersion)" + XCTAssertEqual(sutMisConfigId, expectedMisConfigId) + + let sutMisConfigEmpty = CoreBeacon.getInstanaAgentVersion(hybridAgentId: "", hybridAgentVersion: " ") + let expectedMisConfigEmpty = "\(InstanaSystemUtils.agentVersion)" + XCTAssertEqual(sutMisConfigEmpty, expectedMisConfigEmpty) + } } diff --git a/Tests/InstanaAgentTests/Configuration/InstanaSessionTests.swift b/Tests/InstanaAgentTests/Configuration/InstanaSessionTests.swift index 83c42ec..0612f62 100644 --- a/Tests/InstanaAgentTests/Configuration/InstanaSessionTests.swift +++ b/Tests/InstanaAgentTests/Configuration/InstanaSessionTests.swift @@ -29,7 +29,9 @@ class InstanaSessionTests: InstanaTestCase { // When let configUsi = InstanaConfiguration(reportingURL: .random, key: "KEY", httpCaptureConfig: .automatic, enableCrashReporting: false, slowSendInterval: 0.0, - usiRefreshTimeIntervalInHrs: usiTrackingNotAllowed) + usiRefreshTimeIntervalInHrs: usiTrackingNotAllowed, + hybridAgentId: nil, + hybridAgentVersion: nil) let sut = InstanaSession(configuration: configUsi, propertyHandler: propertyHandler, collectionEnabled: true) // Then @@ -44,7 +46,9 @@ class InstanaSessionTests: InstanaTestCase { // When let configUsi = InstanaConfiguration(reportingURL: .random, key: "KEY", httpCaptureConfig: .automatic, enableCrashReporting: false, slowSendInterval: 0.0, - usiRefreshTimeIntervalInHrs: (1.0 / 3600.0)) + usiRefreshTimeIntervalInHrs: (1.0 / 3600.0), + hybridAgentId: nil, + hybridAgentVersion: nil) let sut = InstanaSession(configuration: configUsi, propertyHandler: propertyHandler, collectionEnabled: true) let oldUsi = sut.usi?.uuidString diff --git a/Tests/InstanaAgentTests/InstanaSetupOptionsTest.swift b/Tests/InstanaAgentTests/InstanaSetupOptionsTest.swift new file mode 100644 index 0000000..90556b0 --- /dev/null +++ b/Tests/InstanaAgentTests/InstanaSetupOptionsTest.swift @@ -0,0 +1,23 @@ +// +// Copyright © 2024 IBM Corp. All rights reserved. +// + +import Foundation +import XCTest +@testable import InstanaAgent + +class HybridAgentOptionsTests: XCTestCase { + func test_init() { + let mco = HybridAgentOptions(id:"f", version:"3.0.6") + AssertEqualAndNotNil(mco.id, "f") + AssertEqualAndNotNil(mco.version, "3.0.6") + + let mcoTooLong = HybridAgentOptions(id:"react-native-agent", version:"2.0.3") + AssertEqualAndNotNil(mcoTooLong.id, "react-native-age") + AssertEqualAndNotNil(mcoTooLong.version, "2.0.3") + + let mcoMisConfiged = HybridAgentOptions(id:"", version:"") + AssertEqualAndNotNil(mcoMisConfiged.id, "") + AssertEqualAndNotNil(mcoMisConfiged.version, "") + } +} diff --git a/Tests/InstanaAgentTests/InstanaTests.swift b/Tests/InstanaAgentTests/InstanaTests.swift index 9ca0670..486e3ec 100644 --- a/Tests/InstanaAgentTests/InstanaTests.swift +++ b/Tests/InstanaAgentTests/InstanaTests.swift @@ -119,6 +119,28 @@ class InstanaTests: InstanaTestCase { AssertEqualAndNotNil(Instana.current?.session.configuration.usiRefreshTimeIntervalInHrs, defaultUsiRefreshTimeIntervalInHrs) } + func test_setupInternal() { + // Given + let key = "KEY" + let reportingURL = URL(string: "http://www.instana.com")! + + let hybridOptions = HybridAgentOptions(id: "f", version:"3.0.6") + _ = Instana.setupInternal(key: key, reportingURL: reportingURL, options: nil, hybridOptions: hybridOptions) + + // Then + AssertEqualAndNotNil(Instana.key, key) + AssertTrue(Instana.collectionEnabled) + AssertTrue(Instana.current!.session.collectionEnabled) + AssertEqualAndNotNil(Instana.sessionID, Instana.current?.session.id.uuidString) + AssertEqualAndNotNil(Instana.reportingURL, reportingURL) + AssertEqualAndNotNil(Instana.current?.session.configuration.reportingURL, reportingURL) + AssertEqualAndNotNil(Instana.current?.session.configuration.httpCaptureConfig, .automatic) + XCTAssertNotEqual(Instana.current?.session.configuration, + .default(key: key, reportingURL: reportingURL, enableCrashReporting: false)) + AssertEqualAndNotNil(Instana.current?.session.configuration.slowSendInterval, 0.0) + AssertEqualAndNotNil(Instana.current?.session.configuration.usiRefreshTimeIntervalInHrs, defaultUsiRefreshTimeIntervalInHrs) + } + func test_setup_disabled() { // Given let key = "KEY" diff --git a/Tests/InstanaAgentTests/Mocks/MockInstanaConfiguration.swift b/Tests/InstanaAgentTests/Mocks/MockInstanaConfiguration.swift index bcc6a3d..929f049 100644 --- a/Tests/InstanaAgentTests/Mocks/MockInstanaConfiguration.swift +++ b/Tests/InstanaAgentTests/Mocks/MockInstanaConfiguration.swift @@ -25,7 +25,9 @@ extension InstanaConfiguration { httpCaptureConfig: httpCaptureConfig, enableCrashReporting: true, slowSendInterval: slowSendInterval, - usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs) + usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs, + hybridAgentId: nil, + hybridAgentVersion: nil) config.suspendReporting = [] config.monitorTypes = [.http, .memoryWarning, diff --git a/Tests/InstanaAgentTests/Utils_Tests/InstanaPersistableQueueTests.swift b/Tests/InstanaAgentTests/Utils_Tests/InstanaPersistableQueueTests.swift index 7f4def4..df57158 100644 --- a/Tests/InstanaAgentTests/Utils_Tests/InstanaPersistableQueueTests.swift +++ b/Tests/InstanaAgentTests/Utils_Tests/InstanaPersistableQueueTests.swift @@ -49,9 +49,11 @@ class InstanaPersistableQueueTests: InstanaTestCase { let sessionID = UUID() let id = Beacon.generateUniqueIdImpl() let beacon1 = CoreBeacon.createDefault(viewName: "View_1", key: "Key_1", timestamp: 0, - sid: sessionID, usi: session.usi, id: id, mobileFeatures: "c") + sid: sessionID, usi: session.usi, id: id, mobileFeatures: "c", + hybridAgentId: "f", hybridAgentVersion: "3.0.6") let beacon2 = CoreBeacon.createDefault(viewName: "View_2", key: "Key_2", timestamp: 0, - sid: sessionID, usi: session.usi, id: id, mobileFeatures: "c") + sid: sessionID, usi: session.usi, id: id, mobileFeatures: "c", + hybridAgentId: "r", hybridAgentVersion: "2.0.3") let queueHandler = InstanaPersistableQueue(identifier: "queue", maxItems: 100) queueHandler.removeAll() @@ -72,14 +74,16 @@ class InstanaPersistableQueueTests: InstanaTestCase { func test_persisted_beacons_plus_new() { // Given let oldBeacons = [CoreBeacon.createDefault(viewName: "V", key: "K", timestamp: 1, sid: UUID(), - usi: session.usi, id: Beacon.generateUniqueIdImpl(), mobileFeatures: "c")] + usi: session.usi, id: Beacon.generateUniqueIdImpl(), mobileFeatures: "c", + hybridAgentId: "f", hybridAgentVersion: "3.0.6"),] var queueHandler = InstanaPersistableQueue(identifier: "queue", maxItems: 100) queueHandler.removeAll() queueHandler.add(oldBeacons) {_ in} // When let newBeacons = [CoreBeacon.createDefault(viewName: "V", key: "K", timestamp: 2, sid: UUID(), - usi: session.usi, id: Beacon.generateUniqueIdImpl(), mobileFeatures: "c")] + usi: session.usi, id: Beacon.generateUniqueIdImpl(), mobileFeatures: "c", + hybridAgentId: "f", hybridAgentVersion: "3.0.6")] queueHandler = InstanaPersistableQueue(identifier: "queue", maxItems: 100) queueHandler.add(newBeacons) {result in AssertTrue(result.error == nil) @@ -97,14 +101,16 @@ class InstanaPersistableQueueTests: InstanaTestCase { // Given let id = Beacon.generateUniqueIdImpl() let oldBeacons = [CoreBeacon.createDefault(viewName: "V", key: "K", timestamp: 1, - sid: UUID(), usi: session.usi, id: id, mobileFeatures: "c")] + sid: UUID(), usi: session.usi, id: id, mobileFeatures: "c", + hybridAgentId: "f", hybridAgentVersion: "3.0.6")] var queueHandler = InstanaPersistableQueue(identifier: "queue", maxItems: 100) queueHandler.removeAll() queueHandler.add(oldBeacons) {_ in} // When let newBeacon = CoreBeacon.createDefault(viewName: "V", key: "K", timestamp: 2, - sid: UUID(), usi: session.usi, id: id, mobileFeatures: "c") + sid: UUID(), usi: session.usi, id: id, mobileFeatures: "c", + hybridAgentId: "r", hybridAgentVersion: "2.0.3") queueHandler = InstanaPersistableQueue(identifier: "queue", maxItems: 100) queueHandler.add(newBeacon) {result in AssertTrue(result.error == nil)