{"id":23191663,"url":"https://github.com/sentryco/telemetric","last_synced_at":"2026-03-05T07:36:40.871Z","repository":{"id":268043635,"uuid":"903197954","full_name":"sentryco/Telemetric","owner":"sentryco","description":"Minimal GA4 telemetrics for iOS and macOS","archived":false,"fork":false,"pushed_at":"2025-01-12T02:59:10.000Z","size":152,"stargazers_count":2,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-01-12T03:27:31.161Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sentryco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-14T01:13:06.000Z","updated_at":"2025-01-12T02:59:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"09e76b6e-5d33-43c1-adf6-4ca9036c30f6","html_url":"https://github.com/sentryco/Telemetric","commit_stats":null,"previous_names":["sentryco/telemetric"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FTelemetric","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FTelemetric/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FTelemetric/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sentryco%2FTelemetric/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sentryco","download_url":"https://codeload.github.com/sentryco/Telemetric/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238128122,"owners_count":19420972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-18T12:18:23.264Z","updated_at":"2025-10-25T11:30:18.614Z","avatar_url":"https://github.com/sentryco.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Tests](https://github.com/sentryco/Telemetric/actions/workflows/Tests.yml/badge.svg)](https://github.com/sentryco/Telemetric/actions/workflows/Tests.yml)\n[![codebeat badge](https://codebeat.co/badges/7079731f-6d84-4a37-9713-2f29c65d1f05)](https://codebeat.co/projects/github-com-sentryco-telemetric-main)\n\n# Telemetric\n\n### Description:\nMinimal GA4 telemetrics for iOS and macOS. \n\n### Problem:\nUsing GA4 in Swift requires the FirebaseSDK, but FirebaseInstanceID, which manages user IDs, is closed-source. This lack of transparency is concerning, especially with Apple's strict user identification rules.\n\n### Solution:\nThis open-source library addresses the issue. It performs the same functions as FirebaseSDK but provides transparency. You can see how personal identification is handled and adjust it to your desired privacy level.\n\n### Features:\n- 📊 Enables bulk sending of events to Google Analytics 4\n- 🗂️ Caches events and processes pings in batches to optimize resource usage\n- 🔐 Provides three levels of user-id persistency: vendor, userDefault, and keychain\n- 🆔 Formats the GA4 clientID with UUID and Date for unique identification \n\n\u003e [!NOTE]\n\u003e Cached events are processed on a timer or when the app exits or enters background mode.\n\n### Pre-requisite:\n- Create a free analytics account on [analytics.coogle.com](analytics.coogle.com) Login with your google account\n- Find your Measurement-ID: [analytics.coogle.com](analytics.coogle.com) -\u003e admin -\u003e data collection... -\u003e data streams -\u003e tap your website / app -\u003e MeasurementID\n- Find your API-Secret: [analytics.coogle.com](analytics.coogle.com) -\u003e admin -\u003e data collection... -\u003e data streams -\u003e tap your website / app -\u003e Measurement Protocol API secrets\n\n### Ping:\nSend single or multiple pings:\n```swift\nlet tracker = Tracker(measurementID: \"G-XXXXXXXXXX\", apiSecret: \"YYYYYYYYYYYYYYYY\")\n// Track events (The modern way to track)\nEvent.customEvent(title: \"view_item_list\", key: \"item_list_name\", value: \"Home Page\"),\n// Track page-views (like legacy UA in GA3)\nEvent.pageView(engagementTimeMSec: \"2400\") // Use page-view to trigger user-engagment event in GA4\n```\n\n### Batch ping:\nConserve resources by intelligently batch processing the pings when the system deems it convenient\n```swift\n// You make your own class with the config you want. \nclass Telemetric: TelemetricKind {\n   static let shared: Telemetric = .init()\n   let tracker = Tracker(\n      measurementID: \"G-XXXXXXXXXX\", // Your GA4 measurement-protocol ID\n      apiSecret: \"YYYYYYYYYYYYYYYY\", // Your GA4 API secret\n      clientID: Identity.uniqueUserIdentifier(type: .vendor) // Apples privacy friendly user id\n   )\n   lazy var collector = EventCollector(batchSize: 10, maxAgeSeconds: 3 * 60) { events in\n      self.tracker.sendEvent(events: events)\n   }\n}\n// This will be processed after 3 minutes\n// or immediately if app closed or goes into the background\n// or immediately if 8 additional events are added to the stack\nTelemetric.shared.send(events: [ // bulk\n   Event(name: \"event3\", params: [\"flagging\": false]),\n   Event(name: \"event4\", params: [\"fiat\": 100])\n])\nTelemetric.shared.send(event: // single\n   Event(name: \"onboarding\", params: [\"progress\": \"complete\"])]\n)\n```\n\n### Session:\n- An internal timetracker records the ellapsed time for the session name\n- This is just a simple example. In a real senario, we would use the EventCollector and batch send these. The code would look more or lese the same. But we would use the Telemetric.shared.send(event:..) call instead\n```swift\nlet tracker = Tracker(measurementID: \"G-XXXXXXXXXX\", apiSecret: \"YYYYYYYYYYYYYYYY\")\ntracker.send(event: Event.session(name: \"onboarding\", isStarting: true)) // init session\nsleep(4) // Simulates time elapsed\ntracker.send(event: Event.session(name: \"onboarding\", isStarting: false)) // end session\n```\n\n### Exceptions: \nUse Cases for Exception Tracking\n- Error Logging: Track application-level errors such as uncaught exceptions or server response errors.\n- Debugging: Collect stack traces or error codes for troubleshooting.\n- Crash Monitoring: Identify and categorize fatal errors that cause crashes.\n```swift\nlet exceptionEvent = Event.exception(description: \"Database unavailable\", isFatal: true, userAction: \"open app\")\ntracker.sendEvent(event: exceptionEvent)\n```\n\n\u003e [!CAUTION]  \n\u003e Google analytics for dashboards are sometimes not very responsive, and sometimes events show up later.\n \n### Swift Package Manager Installation\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/sentryco/Telemetric\", branch: \"main\")\n]\n```\n\n\u003e [!NOTE]\n\u003e 3 levels of userID tracking:  \n\u003e **vendor:**  Persistent between runs (in most cases)   \n\u003e **userDefault:** Persistent between runs  \n\u003e **keychain:** Persistent between installs  \n\n### The Google Analytics 4 (GA4) Measurement Protocol\n\nSend various types of data to GA4 via the endpoint `https://www.google-analytics.com/mp/collect`. Here are the main types of data you can send:\n\n1. **Event Data**: \n   - You can send user interaction events, such as button clicks, page views, or transactions. Each event can include parameters that provide additional context.\n\n2. **User Properties**: \n   - Custom user properties can be sent to define characteristics of users, such as demographics or preferences.\n\n3. **E-commerce Data**: \n   - This includes transaction details, product impressions, and purchase events. You can send information about products, including IDs, names, categories, and prices.\n\n4. **Session Data**: \n   - Information about user sessions can be sent to track session duration and engagement metrics.\n\n5. **App-specific Data**: \n   - For mobile apps, you can send app usage data, including screen views and app installs.\n\n6. **Custom Dimensions and Metrics**: \n   - You can define and send custom dimensions and metrics that are specific to your business needs.\n\n7. **Debugging Information**: \n   - When testing your implementation, you can send debugging information to help troubleshoot issues.\n\n8. **IP Anonymization**: \n   - You can control IP anonymization settings when sending data to comply with privacy regulations.\n\n## Important Considerations\n\n- **Payload Structure**: The data must be structured in a specific JSON format according to GA4's requirements.\n- **Authentication**: Ensure that the requests are authenticated properly if required by your implementation.\n- **Rate Limits**: Be aware of any rate limits imposed by Google Analytics for data collection.\n\nBy utilizing these various data types effectively, you can gain comprehensive insights into user behavior and interactions on your platforms.\n\n\u003e [!IMPORTANT]  \n\u003e For GA4 properties, IP anonymization is always enabled. This means that the last octet of IPv4 user IP addresses and the last 80 bits of IPv6 addresses are set to zeros before any data storage or processing takes place.\n\n### Gotchas:\n- The post body of the request must be smaller than 130kB\n- Each request can have a maximum of 25 events.  100 (GA4 360)\n- Each event can have a maximum of 25 parameters 100 (GA4 360)\n- Parameter names must be 40 characters or fewer, can only contain alpha-numeric characters and underscores, and must start with an alphabetical character.\n- Parameter values must be 100 characters or fewer for standard GA4 properties and 500 characters or fewer for GA4 360 properties\n- User property names must be 24 characters or fewer.\n- User property values must be 36 characters or fewer\n- Append  \"debug_mode\": 1, // or \"debug_mode\": true to event params to use GA4 Debugging Information\n- Append \"session_id\": \"1664522264\" to params and \"timestamp_micros\": \"1664522406546590\", to payload to work with session data \n\n\u003e [!CAUTION]  \n\u003e Nested params in event is not supported by GA4. Use flat structures.\n \n### Resources:\n- Seems to have ways of using the GA4 directly (sort of like the old UA api): https://www.thyngster.com/app-web-google-analytics-measurement-protocol-version-2\n- ga4 endpoint: https://www.google-analytics.com/g/collect\n- Debug endpoint: https://www.google-analytics.com/debug/mp/collect\n\n\u003e [!WARNING]\n\u003e Ensure that the user property you are trying to send has been properly registered in GA4. You need to create the user property in the GA4 Admin section under Custom Definitions before it can be sent with events. Go to GA4 Admin. Under the \"Data display\" section, click on \"Custom definitions\"\n\n### Todo:\n- Figure out how to use: Measurement ID: The unique identifier for your GA4 property. text tid=G-EMPR3SY5D5\n- Add support for user_id (track user across devices and apps)\n- Figure out how to inject apikey and ma-id from github secrets, so that unit-tests works\n- make an assert to make sure posts are less than max allowed at 130kB\n- Remove TimingTracker, I dont think we need it","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsentryco%2Ftelemetric","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsentryco%2Ftelemetric","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsentryco%2Ftelemetric/lists"}