telemetrydeck / swiftclient Goto Github PK
View Code? Open in Web Editor NEWSwift SDK for TelemetryDeck, a privacy-conscious analytics service for apps and websites.
Home Page: https://telemetrydeck.com/
License: Other
Swift SDK for TelemetryDeck, a privacy-conscious analytics service for apps and websites.
Home Page: https://telemetrydeck.com/
License: Other
is true when installed via Xcode on developer's device
Observed: Telemetry uses print-logging.
Expected: os_log
is the modern recommended way of logging. Consider using that. https://developer.apple.com/documentation/os/logging/generating_log_messages_from_your_code
Right now we only return the model name when running the Swift SDK in macOS. Instead, it should return the hardware identifier.
Using IOKit:
public func getMacModel() -> String? {
let service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"))
var modelIdentifier: String?
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
if let modelIdentifierCString = String(data: modelData, encoding: .utf8)?.cString(using: .utf8) {
modelIdentifier = String(cString: modelIdentifierCString)
}
}
IOObjectRelease(service)
return modelIdentifier
}
This will have to be gated with "if OS macOS" calls, so that the SDK still compiles for iOS and Linux.
Hello,
I'm using the SDK in a Mac app, and lately I've noticed:
architecture
graph has automatically switched to modelName
attribute.This was an important data metric for me to track how many of my users were using Apple Silicon vs Intel. Is there something wrong with my query / graph configuration, or is this a bug in the SDK?
I'm using the library version 1.4.0
While skimming through the code, I noticed this line stating the current version of the client to be 1.1.5, but 1.1.6 seems to be already released. Could it be that this version String is manually updated?
Maybe we should think about a solution to ensure it can never be outdated either by making it dynamic or adding some checks (in the build process or CI) to prevent such a thing from happening again. I imagine it could be hard to debug issues if you're searching in the wrong commit. :)
It took a few tries to realize that launching from Xcode would not send Signals — some logging, or including that detail in the documentation would be helpful (likely that I missed it in the docs, if it’s there)
Signal#isAppStore
seems to be wrong in the latest release - it checks for the app store receipt and only returns true if it is named 'sandboxReceipt' which is only the case when running via TestFlight...
AppStore released app is getting 6-12 crashes per day originating from TelemetryClient codebase.
The app is using TelemetryClient v1.3.0.
#0 (null) in __pthread_kill ()
#1 (null) in pthread_kill ()
#2 (null) in abort ()
#3 (null) in swift::fatalErrorv(unsigned int, char const*, char*) ()
#4 (null) in swift::fatalError(unsigned int, char const*, ...) ()
#5 (null) in swift::swift_abortRetainUnowned(void const*) ()
#6 (null) in swift_unownedRetainStrong ()
#7 0x0000000104c12620 in closure #1 in SignalManager.checkForSignalsAndSend() at [...]SwiftClient/Sources/TelemetryClient/SignalManager.swift:129
#8 (null) in thunk for @escaping @callee_guaranteed @Sendable (@guaranteed Data?, @guaranteed NSURLResponse?, @guaranteed Error?) -> () ()
#9 (null) in __40-[__NSURLSessionLocal taskForClassInfo:]_block_invoke ()
#10 (null) in __49-[__NSCFLocalSessionTask _task_onqueue_didFinish]_block_invoke_2 ()
#11 (null) in _dispatch_call_block_and_release ()
#12 (null) in _dispatch_client_callout ()
#13 (null) in _dispatch_lane_serial_drain ()
#14 (null) in _dispatch_lane_invoke ()
#15 (null) in _dispatch_workloop_worker_thread ()
#16 (null) in _pthread_wqthread ()
#17 (null) in start_wqthread ()
Add support for metric kit as signals.
include debug/release mode in signal payload
Possible solutions
This has been floated by a few people asking. Can the client store signals if the internet connection is not good enough. This has some implications:
With this, we'd need to save signals to local storage (Defaults, a temp file, etc). This might be easy, but it might also be a problem if an app sends a lot of signals and fills up storage. Writes and encodes also might use up more CPU than just firing and forgetting the signal. Finally, the client should be able to run on all platforms that support Swift (iOS, macOS, watchOS, tvOS, Linux), and not all storage options might be available on all platforms.
This might need changes on the server: The time the signal arrives at the server is no longer necessarily the time the signal was generated. On the plus side, it might be possible to bundle up signals. Apps that send a lot of signals might use less energy that way, because they don't connect to the net for every signal.
I can't see no serious privacy implications in storing the locally generated signals on the user's device.
if let deviceName = Host.current().localizedName {
print(deviceName)
}
Currently, we only use system version on MacOS. This change will better distinguish users without endangering their privacy
Signals sent from a debug version of the app have their isTestflight
value set to true
. I think this value should be false. Ideally an isDebug
value would also be present.
Setup configuration allowing debug signals:
let configuration = TelemetryManagerConfiguration(appID: "MY_APP_ID")
configuration.telemetryAllowDebugBuilds = true
TelemetryManager.initialize(with: configuration)
TelemetryManager.send("applicationDidFinishLaunching"
Within my personal iOS project I use this code to identify between Debug/TestFlight/AppStore for various other checks. Maybe it will be helpful for this?
public extension Bundle {
enum DistributionSource {
case debug, testFlight, appStore
public var displayableTitle: String {
switch self {
case .debug: return "Debug"
case .testFlight: return "TestFlight"
case .appStore: return "App Store"
}
}
}
static var distributionSource: DistributionSource {
#if DEBUG
return .debug
#else
let isTestFlight = main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
return isTestFlight ? .testFlight : .appStore
#endif
}
}
When included in a watchOS target, this Telemetry library fails to compile with the following errors:
.../SourcePackages/checkouts/SwiftClient/Sources/TelemetryClient/TelemetryClient.swift:138:31: error: cannot find 'UIDevice' in scope
return "\(platform) \(UIDevice.current.systemVersion)"
^~~~~~~~
.../SourcePackages/checkouts/SwiftClient/Sources/TelemetryClient/TelemetryClient.swift:165:16: error: cannot find 'UIDevice' in scope
return UIDevice.current.identifierForVendor?.uuidString ?? "unknown user \(systemVersion) \(buildNumber)"
I have code which is shared between iOS app, iOS Widget and WatchOS app - but I can't see how to differentiate between the code running as an iOS process, or an iOS Widget process.
(The same probably applies to WatchOS or WatchOS Complication)
Can something be added to the standard payload dictionary, to help?
Change the platform
property to (somehow!) detect when running in an iOS Widget or WatchOS Complication.
Include the bundle identifier (Bundle.main.bundleIdentifier
) in the payload dictionary.
Hey! In the set up README for the Swift package it says you'll find your App ID under 'App Settings' but it's actually under 'Set Up App'. Maybe just a nitpick if a little confusing, but I thought you might want to know 😊
When working on my macOS application, which has some TelemetryDeck analytics events being sent on the TCA reducer layer, I received this error while trying to edit a SwiftUI view and tried to see the previews. Unfortunately, previews seem not to work due to TelemetryManage.initialize(...)
not being called.
Given how common SwiftUI is and will become in the future, I think there should be a built-in solution that prevents this from happening. My SwiftUI previews are already all in an if DEBUG
, so if this error didn't happen on debug builds for example, this would help. Or even better, maybe there's a way to detect when run in a preview, this could be auto-detected and some mocked version could be initialized.
Note that my app is modularized using this approach, therefore I don't need to build the top-level App
to see the preview of a component (this is by design for improved build performance). This might be why it's not happening in all projects.
This is based on the repo name I think? Dumb behaviour but the simplest solution is probably just to rename the repo…
Tested on iOS 15.4, with TelemetryDeck 1.1.6 : it seems the default signal is sent only when the notification UIApplication.willEnterForegroundNotification
is received, but that one doesn't seem to be posted on app startup. Is this intended behavior ?
I'm releasing an application soon, I just noticed there's not the ability to disable since we're putting the initialization inside my App's init()
in SwiftUI. I'd like to have an option for the user to disable if they so chose in my apps settings.
init() {
// MARK: ERROR Accessing StateObject's object without being installed on a View. This will create a new instance each time.
/*
if viewmodel.enabled {
} else {
}
*/
// MARK: - Analytics
let configuration = TelemetryManagerConfiguration(appID: "ID_HERE")
TelemetryManager.initialize(with: configuration)
TelemetryManager.send("appLaunchedRegularly")
}
Provide a TelemetryManagerDelegate
protocol with methods that return a default user identifier string and a default payload.
Every time TelemetryManager.send
is called, the method queries wether a delegate is present and what it has to say about default user and payload, mixing in first the built-in payload, then the delegates payload, then the provided payload at method call.
This way, developers do not have to provide these data over and over again, leading to repetition and errors.
Some users might want to reduce the default payloads due to privacy concerns.
There's no license specified for the repo yet
I've got TelemetryDeck working inside my app, and I can see the signals appearing in the viewer app with the generic payload key-val pairs (locale, app version, etc).
However, I can't for the life of me find my custom payload in both the iOS and macOS viewer apps. I am calling TelemetryDeck.send("mySignalName", with: ["key": "val"])
, but don't see any my payload info. I am building and running my app locally, so everything is coming in under 'Test Mode'.
Is there something I'm missing? I followed the docs to get myself to this point.
I am using the latest SDK and viewer app versions (installed about 2 hours ago).
Mac Catalyst apps that are running via TestFlight aren’t reporting as being TestFlight.
Payload looks like:
isAppStore = true
isDebug = false
isSimulator = false
isTestFlight = false
operatingSystem = iOS (even though it’s on my Mac - probably just a Catalyst thing)
platform = macCatalyst
targetEnvironment = macCatalyst
systemVersion = macCatalyst 15.0
In my app, I have places where users can adjust some strings such as file paths to search or ignore. I would like to track how often these fields are being changed (or if they are changed at all), but I don't want to send a signal for every key change a user enters as this would result to too many signals.
Therefore I need to implement an algorithm that limits how often I send this event. While I will probably add this to my app, I thought maybe this might be useful to add right into the client itself for others convenience. Here are 3 additions I would suggest:
send(String, with: [String: String], limit: Int, context: Equatable)
overload where limit
is the max count a signal with the same name can be sent for a given context
, where the context could be a selected item, for example.send(String, with: [String: String], limit: Int, for: TimeInterval)
overload where limit
is the max count a signal with the same name can be sent within a given period of time, so any signals with the same name that exceed this limit are simply dropped and ignored.send(String, with: [String: String], first/last: Int, after: TimeInterval)
overload where first/last
is the max count of first/last signals to be sent after waiting at least the amount of time to pass since the last sending of the signal.With these 3 I could reach my goal of only sending my event once in 3 different ways depending on what I need in a given context. Of course, instead of overloading the send
method, we could also introduce a wrapper type and go for a more function chained API with func limit(count: 1, context: Equatable) -> Self
, func throttle(count: Int = 1, for: TimeInterval) -> Self
, and func debounce(count: Int = 1, after: TimeInterval)
.
I would be up to implement this based on which API design you prefer and which of these 3 you would accept.
At the bottom of the Readme ( under Developing this SDK )
## Developing this SDK
Your PRs on TelemetryDeck's Swift Client are very much welcome. Check out the [SwiftClientTester](https://github.com/TelemetryDeck/SwiftClientTester) project, which provides a harness you can use to work on the library and try out new things.
The link SwiftClientTester
points to a non existing project https://github.com/TelemetryDeck/SwiftClientTester
Expectation:
The link SwiftClientTester
should redirect appropriately
TelemetryClient Version: 1.1.6
I have a macOS app distributed on TestFlight and TelemetryDeck reports it as an App Store app (isAppStore : true, isTestFlight: false
). The signals are not showing up on the test mode dashboard.
Perhaps this tweet will help accurately detect a macOS app under TestFlight distribution and this Stack Overflow answer.
I was just looking over the code and noticed it doesn't check whether the network is up before sending events.
It might be a good idea to configure the session with this property so, instead of blindly sending the request and it failing and then sticking it back in the queue to try again next time, it automatically sends them when the network becomes available.
I just tried to setup TelemetryDeck on the Server (using Vapor) to track some anonymized events of my users API endpoint actions. One reason for this is that I take privacy seriously and therefore don't want to include any library that automatically accesses environment data if I can avoid it. This library for sure does that, so my preferred way to use TelemetryDeck is on the server side, where the environment doesn't provide any user-specific information. I already found out that I can set a user identifier manually, and I saw in the code that any additional payload fields can also override the ones automatically set. This way I will be able to control what data is being tracked of my users and to also implement Differential Privacy on my server if needed (as Telemetry doesn't support that yet).
But even with all these manual actions, this library seems still not to be prepared for Server-side usage as it's not only failing to recognize Linux as a platform automatically, but is also using DispatchQueue for doing asynchronous network calls, which I think is not the right way to go for a Vapor app. I'd expect some setting to run the network requests in an EventLoop
instead (using Swift NIO).
@winsmith Do you have plans to add proper support for usage from the Server side? Or is this only intended for app clients?
I would like to enable sending signals while in debug build. This would make for easier setup and integration.
In my current analytics tool I choose different backend ids depending on debug / release builds to separate my testing data from production data. It's not a big blocker as there are ways around, but would be great to have support for this as well.
I guess that sessions, when implemented, can be filtered by their user
identifier. So if a customer sends a support email via my app, I'd like to include that user
ID in the body of the email.
The TelemetryManager.defaultUserIdentifier
property is internal
- so I can't include it in the email body.
Alternatively, I could make my own user
UUID, store it somewhere, and then supply it with every call to TelemetryManager.send(...)
. But that's a very repetitive process.
Would it be possible to set a custom user
value once - either in TelemetryManagerConfiguration
, or TelemetryManager
itself - and have that value used for every signal?
(I'd be happy to make a pull request - but would like some guidance first about your preferred approach!)
I just upgraded from version 1.0.13 -> 1.1.3, and the SDK fails to compile. I'm using this for a macOS only app, and my deployment target is macOS 10.13+.
The error is in the file Signal.swift
for an availability check for the property isiOSAppOnMac
isiOSAppOnMac' is only available in macOS 11.0 or newer
Versions upto 1.1.1
work fine. The issue starts with version >= 1.1.2
.
It appears that isAppStore
is true when the build is not on a simulator, and not on TestFlight.
However, if I build from Xcode onto a physical device then this makes it show as "isAppStore: true" which is incorrect and a lil misleading.
[bug]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.