From e110809469b33d3ccc355a6c87a99d32e054c7be Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Thu, 17 Oct 2024 11:11:19 -0400 Subject: [PATCH] Publish version 1.3.0 --- CHANGELOG.md | 16 +++ Editor/DatadogConfigurationWindow.cs | 80 ++++++++----- Editor/DatadogDependencies.xml | 14 +-- Editor/DatadogHelpStrings.cs | 97 ++++++++++++++++ Editor/DatadogHelpStrings.cs.meta | 3 + Editor/iOS/PostBuildProcess.cs | 12 +- Plugins/iOS/DatadogLogging_Bridge.swift | 58 +++++----- Plugins/iOS/Datadog_Bridge.swift | 57 +++++++++ README.md | 108 +---------------- Runtime/Android/DatadogAndroidHelpers.cs | 23 +++- Runtime/Android/DatadogAndroidLogger.cs | 33 +++++- Runtime/Android/DatadogAndroidPlatform.cs | 87 +++++++++++++- Runtime/Android/DatadogAndroidRum.cs | 31 ++++- Runtime/AssemblyInfo.cs | 2 +- Runtime/DatadogConfigurationOptions.cs | 29 +++++ Runtime/DatadogNoopPlatform.cs | 6 + Runtime/DatadogPlatform.cs | 3 + Runtime/DatadogSdk.cs | 7 +- Runtime/DdSdkProcessor.cs | 4 +- Runtime/Il2CppErrorHelper.cs | 122 ++++++++++++++++++++ Runtime/Il2CppErrorHelper.cs.meta | 3 + Runtime/InternalLogger.cs | 4 +- Runtime/Logs/DatadogLoggingOptions.cs | 7 ++ Runtime/Logs/DdLogProcessor.cs | 12 +- Runtime/Rum/DatadogTrackedWebRequest.cs | 6 + Runtime/Rum/DdRumProcessor.cs | 24 ++-- Runtime/Worker/ThreadSafeObjectPool.cs | 74 ++++++++++++ Runtime/Worker/ThreadSafeObjectPool.cs.meta | 3 + Runtime/iOS/DatadogiOSLogger.cs | 26 +++-- Runtime/iOS/DatadogiOSPlatform.cs | 101 +++++++++++++++- Runtime/iOS/DatadogiOSRum.cs | 26 ++++- package.json | 2 +- 32 files changed, 859 insertions(+), 221 deletions(-) create mode 100644 Editor/DatadogHelpStrings.cs create mode 100644 Editor/DatadogHelpStrings.cs.meta create mode 100644 Runtime/Il2CppErrorHelper.cs create mode 100644 Runtime/Il2CppErrorHelper.cs.meta create mode 100644 Runtime/Worker/ThreadSafeObjectPool.cs create mode 100644 Runtime/Worker/ThreadSafeObjectPool.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index f40c312..d5f88ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log +## 1.3.0 + +* Added an option for detecting non-fatal ANRs on Android. +* Added an option for detecting non-fatal app hangs within a given threshold on iOS. +* Added documentation tooltips to the Datadog Options window (on Labels). +* Added support for file / line mappings in C# exceptions through C# Native Stack Mapping +* Make DatadogWorker's message pools thread safe. +* Stop Resources in DatadogTrackedWebRequest if the underlying UnityWebRequest is Disposed. +* Updated iOS SDK to 2.17.0 + * Memory warnings are now tracked as RUM errors + * Fix refresh rate vital for variable refresh rate displays +* Updated Android SDK to 2.14.0 + * Increase retry delay on DNS error. + * Stop upload worker on upload failure + * Ensure `UploadWorker` uses the SDK instance name. + ## 1.2.0 * Add and option to control when trace context headers are injected into web requests (Trace Context Injection). diff --git a/Editor/DatadogConfigurationWindow.cs b/Editor/DatadogConfigurationWindow.cs index aa0e47a..1abb95f 100644 --- a/Editor/DatadogConfigurationWindow.cs +++ b/Editor/DatadogConfigurationWindow.cs @@ -39,54 +39,80 @@ public override void OnGUI(string searchContext) GUILayout.Label("SDK Options", EditorStyles.boldLabel); _options.Enabled = EditorGUILayout.ToggleLeft( - new GUIContent("Enable Datadog", "Whether the Datadog Plugin should be enabled or not."), + new GUIContent("Enable Datadog", DatadogHelpStrings.EnabledTooltip), _options.Enabled); _options.OutputSymbols = EditorGUILayout.ToggleLeft( new GUIContent( "Output Symbol Files", - "Whether the Datadog Plugin should output symbol files for crash reporting."), + DatadogHelpStrings.OutputSymbolsTooltip), _options.OutputSymbols); + EditorGUI.BeginDisabledGroup(!_options.OutputSymbols); + _options.PerformNativeStackMapping = EditorGUILayout.ToggleLeft( + new GUIContent("Perform Native Stack Mapping", DatadogHelpStrings.PerformNativeStackMappingTooltip), + _options.PerformNativeStackMapping); + EditorGUI.EndDisabledGroup(); - _options.ClientToken = EditorGUILayout.TextField("Client Token", _options.ClientToken); - _options.Env = EditorGUILayout.TextField("Env", _options.Env); - _options.ServiceName = EditorGUILayout.TextField("Service Name", _options.ServiceName); - _options.Site = (DatadogSite)EditorGUILayout.EnumPopup("Datadog Site", _options.Site); - _options.BatchSize = (BatchSize)EditorGUILayout.EnumPopup("Batch Size", _options.BatchSize); - _options.UploadFrequency = (UploadFrequency)EditorGUILayout.EnumPopup("Upload Frequency", _options.UploadFrequency); - _options.BatchProcessingLevel = (BatchProcessingLevel)EditorGUILayout.EnumPopup("Batch Processing Level", _options.BatchProcessingLevel); + _options.ClientToken = EditorGUILayout.TextField(new GUIContent("Client Token", DatadogHelpStrings.ClientTokenTooltip), _options.ClientToken); + _options.Env = EditorGUILayout.TextField(new GUIContent("Env", DatadogHelpStrings.EnvTooltip), _options.Env); + _options.ServiceName = EditorGUILayout.TextField(new GUIContent("Service Name", DatadogHelpStrings.ServiceNameTooltip), _options.ServiceName); + _options.Site = (DatadogSite)EditorGUILayout.EnumPopup(new GUIContent("Datadog Site", DatadogHelpStrings.SiteTooltip), _options.Site); + _options.BatchSize = (BatchSize)EditorGUILayout.EnumPopup(new GUIContent("Batch Size", DatadogHelpStrings.BatchSizeTooltip), _options.BatchSize); + _options.UploadFrequency = (UploadFrequency)EditorGUILayout.EnumPopup( + new GUIContent("Upload Frequency", DatadogHelpStrings.UploadFrequencyTooltip), _options.UploadFrequency); + _options.BatchProcessingLevel = (BatchProcessingLevel)EditorGUILayout.EnumPopup( + new GUIContent("Batch Processing Level", DatadogHelpStrings.BatchProcessingLevelTooltip), _options.BatchProcessingLevel); _options.CrashReportingEnabled = EditorGUILayout.ToggleLeft( - new GUIContent("Enable Crash Reporting", "Whether to report native crashes to Datadog."), + new GUIContent("Enable Crash Reporting", DatadogHelpStrings.CrashReportingEnabledTooltip), _options.CrashReportingEnabled); EditorGUILayout.Space(); GUILayout.Label("Logging", EditorStyles.boldLabel); _options.ForwardUnityLogs = EditorGUILayout.ToggleLeft( - new GUIContent("Forward Unity Logs", "Whether calls to Debug.Log functions should be forwarded to Datadog."), + new GUIContent("Forward Unity Logs", DatadogHelpStrings.ForwardUnityLogsTooltip), _options.ForwardUnityLogs); - _options.RemoteLogThreshold = (LogType)EditorGUILayout.EnumPopup("Remote Log Threshold", _options.RemoteLogThreshold); + _options.RemoteLogThreshold = (LogType)EditorGUILayout.EnumPopup( + new GUIContent("Remote Log Threshold", DatadogHelpStrings.RemoteLogThresholdTooltip), _options.RemoteLogThreshold); EditorGUILayout.Space(); GUILayout.Label("RUM Options", EditorStyles.boldLabel); _options.RumEnabled = EditorGUILayout.ToggleLeft( - new GUIContent("Enable RUM", "Whether to enable Real User Monitoring (RUM)"), + new GUIContent("Enable RUM", DatadogHelpStrings.EnableRumTooltip), _options.RumEnabled); EditorGUI.BeginDisabledGroup(!_options.RumEnabled); + _options.RumApplicationId = EditorGUILayout.TextField( + new GUIContent("RUM Application Id",DatadogHelpStrings.RUMApplicationIdTooltip), _options.RumApplicationId); _options.AutomaticSceneTracking = EditorGUILayout.ToggleLeft( - new GUIContent("Enable Automatic Scene Tracking", "Automatically start Datadog Views when Unity Scenes change"), + new GUIContent("Enable Automatic Scene Tracking", DatadogHelpStrings.EnableSceneTrackingTooltip), _options.AutomaticSceneTracking); - _options.RumApplicationId = EditorGUILayout.TextField("RUM Application Id", _options.RumApplicationId); - _options.SessionSampleRate = - EditorGUILayout.FloatField("Session Sample Rate", _options.SessionSampleRate); + _options.SessionSampleRate = EditorGUILayout.FloatField( + new GUIContent("Session Sample Rate", DatadogHelpStrings.SessionSampleRateTooltip), + _options.SessionSampleRate); _options.SessionSampleRate = Math.Clamp(_options.SessionSampleRate, 0.0f, 100.0f); - _options.TraceSampleRate = - EditorGUILayout.FloatField("Trace Sample Rate", _options.TraceSampleRate); - _options.TraceContextInjection = - (TraceContextInjection)EditorGUILayout.EnumPopup("Trace Context Injection", _options.TraceContextInjection); + _options.TraceSampleRate = EditorGUILayout.FloatField( + new GUIContent("Trace Sample Rate", DatadogHelpStrings.TraceSampleRateTooltip), + _options.TraceSampleRate); + _options.TraceContextInjection = (TraceContextInjection)EditorGUILayout.EnumPopup( + new GUIContent("Trace Context Injection", DatadogHelpStrings.TraceContextInjectionTooltip), + _options.TraceContextInjection); _options.TraceSampleRate = Math.Clamp(_options.TraceSampleRate, 0.0f, 100.0f); + _options.TrackNonFatalAnrs = (NonFatalAnrDetectionOption)EditorGUILayout.EnumPopup( + new GUIContent("Track Non-Fatal ANRs", DatadogHelpStrings.TrackNonFatalAnrsTooltip), + _options.TrackNonFatalAnrs); + EditorGUILayout.BeginHorizontal(); + _options.TrackNonFatalAppHangs = EditorGUILayout.ToggleLeft( + new GUIContent("Track Non-Fatal App Hangs", DatadogHelpStrings.TrackNonFatalAppHangsTooltip), + _options.TrackNonFatalAppHangs); + EditorGUI.BeginDisabledGroup(!_options.TrackNonFatalAppHangs); + _options.NonFatalAppHangThreshold = EditorGUILayout.FloatField( + new GUIContent("Threshold", DatadogHelpStrings.NonFatalAppHangThresholdTooltip), + _options.NonFatalAppHangThreshold); + EditorGUI.EndDisabledGroup(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); GUILayout.Space(12.0f); - GUILayout.Label("First Party Hosts", EditorStyles.boldLabel); + GUILayout.Label(new GUIContent("First Party Hosts", DatadogHelpStrings.FirstPartyHostsTooltip), EditorStyles.boldLabel); int toRemove = -1; for (int i = 0; i < _options.FirstPartyHosts.Count; ++i) { @@ -118,10 +144,12 @@ public override void OnGUI(string searchContext) _showAdvancedOptions = EditorGUILayout.BeginFoldoutHeaderGroup(_showAdvancedOptions, "Advanced RUM Options"); if (_showAdvancedOptions) { - _options.CustomEndpoint = EditorGUILayout.TextField("Custom Endpoint", _options.CustomEndpoint); - _options.SdkVerbosity = (CoreLoggerLevel)EditorGUILayout.EnumPopup("SDK Verbosity", _options.SdkVerbosity); - _options.TelemetrySampleRate = - EditorGUILayout.FloatField("Telemetry Sample Rate", _options.TelemetrySampleRate); + _options.CustomEndpoint = EditorGUILayout.TextField( + new GUIContent("Custom Endpoint", DatadogHelpStrings.CustomEndpointTooltip), _options.CustomEndpoint); + _options.SdkVerbosity = (CoreLoggerLevel)EditorGUILayout.EnumPopup( + new GUIContent("SDK Verbosity", DatadogHelpStrings.SdkVerbosityTooltip), _options.SdkVerbosity); + _options.TelemetrySampleRate = EditorGUILayout.FloatField( + new GUIContent("Telemetry Sample Rate", DatadogHelpStrings.TelemetrySampleRateTooltip), _options.TelemetrySampleRate); _options.TelemetrySampleRate = Math.Clamp(_options.TelemetrySampleRate, 0.0f, 100.0f); } EditorGUILayout.EndFoldoutHeaderGroup(); diff --git a/Editor/DatadogDependencies.xml b/Editor/DatadogDependencies.xml index f7790d1..0292723 100644 --- a/Editor/DatadogDependencies.xml +++ b/Editor/DatadogDependencies.xml @@ -4,17 +4,17 @@ https://repo.maven.apache.org/maven2 https://oss.sonatype.org/content/repositories/snapshots - + - + - + - - - - + + + + \ No newline at end of file diff --git a/Editor/DatadogHelpStrings.cs b/Editor/DatadogHelpStrings.cs new file mode 100644 index 0000000..fc06144 --- /dev/null +++ b/Editor/DatadogHelpStrings.cs @@ -0,0 +1,97 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024-Present Datadog, Inc. + +// ReSharper disable InconsistentNaming +namespace Datadog.Unity.Editor +{ + /// + /// Class to make editing tooltips and help strings easier. + /// + public static class DatadogHelpStrings + { + public const string EnabledTooltip = + "Enable or disable the Datadog Unity SDK. When disabled, the SDK will not send any data to Datadog."; + + public const string OutputSymbolsTooltip = + "Whether to output symbol files as part of the build. Uploading these files to Datadog enables symbolication " + + "of native stack traces."; + + public const string PerformNativeStackMappingTooltip = + "Converts C# stacks traces to native stack traces in non-development builds. This allows for file and line " + + "mapping to C# code if symbol files are uploaded to Datadog. This is not supported if Output Symbols is disabled."; + + public const string ClientTokenTooltip = "This is your Client Token created for your application on Datadog’s website."; + + public const string EnvTooltip = + "The environment name that will be sent with each event. This can be used to filter your events on " + + "different environments (e.g. \"staging\" vs. \"production\")."; + + public const string ServiceNameTooltip = + "The service name for your application. If this is not set it will be set to your application's package " + + "name or bundle name (e.g.: com.example.android)"; + + public const string SiteTooltip = "The Datadog site to send data to."; + + public const string BatchSizeTooltip = + "Sets the preferred size of batched data uploaded to Datadog. This value impacts the size and number of " + + "requests performed by the SDK (small batches mean more requests, but each request becomes smaller in size)."; + + public const string UploadFrequencyTooltip = + "Sets the preferred frequency of uploading data to Datadog."; + + public const string BatchProcessingLevelTooltip = + "Defines the maximum amount of batches processed sequentially without a delay within one reading/uploading cycle."; + + public const string CrashReportingEnabledTooltip = "Enables crash reporting in the RUM SDK."; + + public const string ForwardUnityLogsTooltip = + "Whether to forward logs made from Unity’s Debug.Log calls to Datadog’s default logger."; + + public const string RemoteLogThresholdTooltip = + "The level at which the default logger forwards logs to Datadog. Logs below this level are not sent."; + + public const string EnableRumTooltip = + "Whether to enable sending data from Datadog’s Real User Monitoring APIs"; + + public const string RUMApplicationIdTooltip = + "The RUM Application ID created for your application on Datadog’s website."; + + public const string EnableSceneTrackingTooltip = + "Whether Datadog should automatically track new Views by intercepting Unity’s SceneManager loading."; + + public const string SessionSampleRateTooltip = + "The percentage rate at which sessions are sampled. A value of 100 means all sessions are sampled and sent to " + + "Datadog. 50 means 50% of sessions are sampled and sent to Datadog."; + + public const string TraceSampleRateTooltip = + "The percentage rate at which distributed traces are sampled. A value of 100 means all traces are sampled and sent to " + + "Datadog. 50 means 50% of traces are sampled and sent to Datadog."; + + public const string TraceContextInjectionTooltip = + "Defines whether the context for a distributed trace should be injected into all requests or only into requests that are sampled in."; + + public const string TrackNonFatalAnrsTooltip = + "(Android Only) Wether to track non-fatal ANRs (Application Not Responding) errors. The \"SDK Default\" option disables ANR" + + "detection on Android 30+ because it would create too much noise over fatal ANRs. On Android 29 and below, however, " + + "the reporting of non-fatal ANRs is enabled by default, as fatal ANRs cannot be reported on those versions."; + + public const string TrackNonFatalAppHangsTooltip = + "(iOS Only) Whether to track non-fatal app hangs. App hangs are detected when the app is unresponsive for a certain amount of time."; + + public const string NonFatalAppHangThresholdTooltip = + "The amount of time in seconds that the app must be unresponsive before it is considered a non-fatal app hang."; + + public const string FirstPartyHostsTooltip = + "To enable distributed tracing, you must specify which hosts are considered “first party” and have trace information injected."; + + public const string CustomEndpointTooltip = + "Send data to a custom endpoint instead of the default Datadog endpoint. This is useful for proxying data through a custom server."; + + public const string SdkVerbosityTooltip = + "The level of debugging information the Datadog SDK should output. Higher levels will output more information."; + + public const string TelemetrySampleRateTooltip = + "The percentage rate at which Datadog sends internal telemetry data. A value of 100 means all telemetry data is sampled and sent to Datadog."; + } +} diff --git a/Editor/DatadogHelpStrings.cs.meta b/Editor/DatadogHelpStrings.cs.meta new file mode 100644 index 0000000..6b2c8b2 --- /dev/null +++ b/Editor/DatadogHelpStrings.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: df6f98789639476ea35574b2ddace175 +timeCreated: 1725897539 \ No newline at end of file diff --git a/Editor/iOS/PostBuildProcess.cs b/Editor/iOS/PostBuildProcess.cs index 87cf13d..b74d006 100644 --- a/Editor/iOS/PostBuildProcess.cs +++ b/Editor/iOS/PostBuildProcess.cs @@ -4,6 +4,7 @@ #if UNITY_EDITOR_OSX using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -123,6 +124,7 @@ func initializeDatadog() {{ var additionalConfigurationItems = new List() { $" \"{DatadogSdk.ConfigKeys.Source}\": \"unity\"", + $" \"{DatadogSdk.ConfigKeys.NativeSourceType}\": \"ios+il2cpp\"", }; if (buildId != null) @@ -176,11 +178,15 @@ func initializeDatadog() {{ sb.AppendLine($" rumConfig.sessionSampleRate = {options.SessionSampleRate}"); sb.AppendLine($" rumConfig.telemetrySampleRate = {options.TelemetrySampleRate}"); + var appHangThreshold = + options.TrackNonFatalAppHangs ? options.NonFatalAppHangThreshold.ToString(CultureInfo.InvariantCulture) : "nil"; + sb.AppendLine( + $" rumConfig.appHangThreshold = {appHangThreshold}"); // Uncomment to enable RUM Configuration Telemetry - // sb.AppendLine(@" rumConfig._internal_mutation { - // $0.configurationTelemetrySampleRate = 100.0 - // }"); + // sb.AppendLine(@" rumConfig._internal_mutation { + // $0.configurationTelemetrySampleRate = 100.0 + // }"); sb.AppendLine(" RUM.enable(with: rumConfig)"); } diff --git a/Plugins/iOS/DatadogLogging_Bridge.swift b/Plugins/iOS/DatadogLogging_Bridge.swift index 1c63abe..9210097 100644 --- a/Plugins/iOS/DatadogLogging_Bridge.swift +++ b/Plugins/iOS/DatadogLogging_Bridge.swift @@ -38,7 +38,7 @@ extension Logs.Configuration: Decodable { extension Logger.Configuration: Decodable { public init(from decoder:Decoder) throws { self.init() - + let values = try decoder.container(keyedBy: CodingKeys.self) service = try values.decode(String?.self, forKey: CodingKeys.service) name = try values.decode(String?.self, forKey: CodingKeys.name) @@ -89,39 +89,39 @@ func DatadogLogging_Log( attributes: UnsafeMutablePointer?, error: UnsafeMutablePointer?) { - guard let logId = logId, let message = message else { - return - } + guard let logId = logId, let message = message else { + return + } - if let idString = String(cString: logId, encoding: .utf8), - let logger = LogRegistry.shared.logs[idString], - let swiftMessage = String(cString: message, encoding: .utf8) { + if let idString = String(cString: logId, encoding: .utf8), + let logger = LogRegistry.shared.logs[idString], + let swiftMessage = String(cString: message, encoding: .utf8) { - var decodedAttributes: [String: Encodable]? - if let jsonAttributes = decodeJsonCString(cString: attributes) { - decodedAttributes = castJsonAttributesToSwift(jsonAttributes) - } + var decodedAttributes: [String: Encodable]? + if let jsonAttributes = decodeJsonCString(cString: attributes) { + decodedAttributes = castJsonAttributesToSwift(jsonAttributes) + } - var errorKind: String? - var errorMessage: String? - var stackTrace: String? - if let jsonError = decodeJsonCString(cString: error) { - errorKind = jsonError["type"] as? String - errorMessage = jsonError["message"] as? String - stackTrace = jsonError["stackTrace"] as? String - } + var errorKind: String? + var errorMessage: String? + var stackTrace: String? + if let jsonError = decodeJsonCString(cString: error) { + errorKind = jsonError["type"] as? String + errorMessage = jsonError["message"] as? String + stackTrace = jsonError["stackTrace"] as? String + } - let logLevel = LogLevel(rawValue: logLevel) ?? .info - logger._internal.log( - level: logLevel, - message: swiftMessage, - errorKind: errorKind, - errorMessage: errorMessage, - stackTrace: stackTrace, - attributes: decodedAttributes - ) + let logLevel = LogLevel(rawValue: logLevel) ?? .info + logger._internal.log( + level: logLevel, + message: swiftMessage, + errorKind: errorKind, + errorMessage: errorMessage, + stackTrace: stackTrace, + attributes: decodedAttributes + ) + } } -} @_cdecl("DatadogLogging_AddTag") func DatadogLogging_AddTag(logId: UnsafeMutablePointer?, tag: UnsafeMutablePointer?, value: UnsafeMutablePointer?) { diff --git a/Plugins/iOS/Datadog_Bridge.swift b/Plugins/iOS/Datadog_Bridge.swift index e84edab..4b41205 100644 --- a/Plugins/iOS/Datadog_Bridge.swift +++ b/Plugins/iOS/Datadog_Bridge.swift @@ -6,6 +6,7 @@ import Foundation import DatadogCore import DatadogLogs import DatadogInternal +import MachO @_cdecl("Datadog_SetSdkVerbosity") func Datadog_SetSdkVerbosity(sdkVerbosityInt: Int) { @@ -123,3 +124,59 @@ func Datadog_UpdateTelemetryConfiguration(unityVersion: CString) { let core = CoreRegistry.default core.telemetry.configuration(unityVersion: unityVersion) } + +@_cdecl("Datadog_FindImageLoadAddress") +func Datadog_FindImageLoadAddress(imageUuid: CString) -> Int { + guard let imageUuid = decodeCString(cString: imageUuid), + let imageUuid = UUID(uuidString: imageUuid) else { + return 0 + } + + let numImages = _dyld_image_count() + var loadAddress: Int? + for i in 0..? + let dlInfo = UnsafeMutablePointer.allocate(capacity: 1) + if (dladdr(header, dlInfo) == 0) { + continue + } + + if header.pointee.magic == MH_MAGIC_64 || header.pointee.magic == MH_CIGAM_64 { + header.withMemoryRebound(to: mach_header_64.self, capacity: 1) { + $0.successor().withMemoryRebound(to: load_command.self, capacity: 1, { lc in + loadCommand = lc + }) + } + } else { + header.successor().withMemoryRebound(to: load_command.self, capacity: 1, { lc in + loadCommand = lc + }) + } + + for i in 0...header.pointee.ncmds { + guard let currentCommand = loadCommand else { + return 0 + } + + if currentCommand.pointee.cmd == LC_UUID { + currentCommand.withMemoryRebound(to: uuid_command.self, capacity: 1) { pointer in + let commandUuid = UUID(uuid: pointer.pointee.uuid) + if commandUuid == imageUuid { + loadAddress = Int(bitPattern: dlInfo.pointee.dli_fbase) + } + } + + if let loadAddress = loadAddress { + return loadAddress + } + } + + let nextCommand = UnsafeRawPointer(currentCommand)?.advanced(by: Int(currentCommand.pointee.cmdsize)) + loadCommand = nextCommand?.assumingMemoryBound(to: load_command.self) + } + } + } + + return loadAddress ?? 0 +} diff --git a/README.md b/README.md index 03c2cf1..88aacc0 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,14 @@ The Datadog Unity SDK supports logging and crash reporting for Android and iOS a 1. Install [External Dependency Manager for Unity (EDM4U)](https://github.com/googlesamples/unity-jar-resolver). This can be done using [Open UPM](https://openupm.com/packages/com.google.external-dependency-manager/). -2. Disable static linking for iOS in EDM4U. This can be found in Assets -> External Dependency Manager -> iOS Resolver -> Settings -> Link frameworks statically. If this option is left enabled, you will see errors that Datadog was not initialized when attempting to create loggers or start views. - -3. Add the Datadog SDK Unity package from its Git URL at [https://github.com/DataDog/unity-package](https://github.com/DataDog/unity-package). The package url is `https://github.com/DataDog/unity-package.git`. +2. Add the Datadog SDK Unity package from its Git URL at [https://github.com/DataDog/unity-package](https://github.com/DataDog/unity-package). The package url is `https://github.com/DataDog/unity-package.git`. > [!NOTE] -> Datadog plans on adding support for Open UPM after Closed Beta. +> Datadog plans on adding support for Open UPM after Beta. 4. Configure your project to use [Gradle templates](https://docs.unity3d.com/Manual/gradle-templates.html), and enable both `Custom Main Template` and `Custom Gradle Properties Template`. -5. If you build and recieve `Duplicate class` errors (common in Unity 2022.x) add the following block in the `dependencies` block in your `mainTemplate.gradle`: +5. If you build and receive `Duplicate class` errors (common in Unity 2022.x), add the following block in the `dependencies` block in your `mainTemplate.gradle`: ```groovy constraints { @@ -31,105 +29,9 @@ The Datadog Unity SDK supports logging and crash reporting for Android and iOS a } ``` -## Setup - -1. In Datadog, navigate to [UX Monitoring > Setup & Configuration > New Application](https://app.datadoghq.com/rum/application/create) - -2. Choose `Unity` as the application type. If you do not see `Unity` as an application type, please reach out to your CSM to be added to the Unity beta. - -3. After adding the Datadog Unity SDK, configure Datadog from your Project Settings: - a. Enable Datadog and RUM - b. Copy your `Client Token` and `Application Id` into the fields in the settings window. - c. Verify that your `Site` is correct. - -# Using Datadog - -## Setting Tracking Consent - -In order to be compliant with data protection and privacy policies, the Datadog Unity SDK requires setting a tracking consent value. - -The `trackingConsent` setting can be one of the following values: - - * `TrackingConsent.Pending`: The Unity SDK starts collecting and batching the data but does not send it to Datadog. The Unity SDK waits for the new tracking consent value to decide what to do with the batched data. - * `TrackingConsent.Granted`: The Unity SDK starts collecting the data and sends it to Datadog. - * `TrackingConsent.NotGranted`: The Unity SDK does not collect any data. No logs are sent to Datadog. - -Before Datadog sends any data, we need to confirm the user's `Tracking Consent`. This is set to `TrackingConsent.Pending` during initialization, -and needs to be set to `TrackingConsent.Granted` before Datadog sends any information. - -```cs -DatadogSdk.Instance.SetTrackingConsent(TrackingConsent.Granted); -``` - -## Logging - -You can intercept and send logs from Unity's default debug logger by enabling the option and threshold in your projects settings. - -Datadog maps the Unity levels to the following in Datadog's Logging Levels: - -| Unity LogType | Datadog Log Level | -| -------------- | ----------------- | -| Log | Info | -| Error | Error | -| Assert | Critical | -| Warning | Warn | -| Exception | Critical | - -You can access this default logger to add attributes or tags through the `DatadogSdk.DefaultLogger` property. - -You can also create additional loggers for more fine grained control of thresholds, service names, logger names, or to supply additional attributes. - -```cs -var logger = DatadogSdk.Instance.CreateLogger(new DatadogLoggingOptions() -{ - SendNetworkInfo = true, - DatadogReportingThreshold = DdLogLevel.Debug, -}); -logger.Info("Hello from Unity!"); - -logger.Debug("Hello with attributes", new() -{ - { "my_attribute", 122 }, - { "second_attribute", "with_value" }, - { "bool_attribute", true }, - { - "nested_attribute", new Dictionary() - { - { "internal_attribute", 1.234 }, - } - }, -}); -``` - -## Real User Monitoring (RUM) - -### Manual Scene (View) Tracking - -To manually track new Scenes (`Views` in Datadog), use the `StartView` and `StopView` methods: - -```cs -public void Start() -{ - DatadogSdk.Instance.Rum.StartView("My View", new() - { - { "view_attribute": "active" } - }); -} -``` - -Starting a new view automatically ends the previous view. - -### Automatic Scene Tracking - -You can also set `Enable Automatic Scene Tracking` in your Project Settings to enable automatically tracking active scenes. This uses Unity's `SceneManager.activeSceneChanged` event to automatically start new scenes. - -### Web Requests / Resource Tracking - -Datadog offers `DatadogTrackedWebRequest`, which is a `UnityWebRequest` wrapper intended to be a drop-in replacement for `UnityWebRequest`. `DatadogTrackedWebRequest` enables [Datadog Distributed Tracing](https://docs.datadoghq.com/real_user_monitoring/connect_rum_and_traces/?tab=browserrum). - -To enable Datadog Distributed Tracing, you must set the `First Party Hosts` in your project settings to a domain that supports distributed tracing. You can also modify the sampling rate for distributed tracing by setting the `Tracing Sampling Rate`. +## Additional Setup and Documentation -`First Party Hosts` does not allow wildcards, but matches any subdomains for a given domain. For example, api.example.com matches staging.api.example.com and prod.api.example.com, but not news.example.com. +For further instructions on how to set up the Datadog SDK, refer to the [RUM Unity Monitoring Setup documentation](https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/setup/unity/). ## Contributing diff --git a/Runtime/Android/DatadogAndroidHelpers.cs b/Runtime/Android/DatadogAndroidHelpers.cs index 9c69b06..273535c 100644 --- a/Runtime/Android/DatadogAndroidHelpers.cs +++ b/Runtime/Android/DatadogAndroidHelpers.cs @@ -10,13 +10,28 @@ namespace Datadog.Unity.Android { public static class DatadogAndroidHelpers { + private static IntPtr _hashMapPutMethodId; + + public static IntPtr hashMapPutMethodId + { + get + { + if (_hashMapPutMethodId == IntPtr.Zero) + { + _hashMapPutMethodId = AndroidJNIHelper.GetMethodID( + new AndroidJavaObject("java.util.HashMap").GetRawClass(), + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + } + + return _hashMapPutMethodId; + } + } + public static AndroidJavaObject DictionaryToJavaMap(IDictionary attributes) { var javaMap = new AndroidJavaObject("java.util.HashMap"); - IntPtr putMethod = AndroidJNIHelper.GetMethodID( - javaMap.GetRawClass(), - "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + IntPtr putMethod = hashMapPutMethodId; if (attributes != null) { diff --git a/Runtime/Android/DatadogAndroidLogger.cs b/Runtime/Android/DatadogAndroidLogger.cs index 210435a..7bdaa33 100644 --- a/Runtime/Android/DatadogAndroidLogger.cs +++ b/Runtime/Android/DatadogAndroidLogger.cs @@ -13,11 +13,13 @@ namespace Datadog.Unity.Android internal class DatadogAndroidLogger : DdLogger { private readonly AndroidJavaObject _androidLogger; + private readonly DatadogAndroidPlatform _androidPlatform; - public DatadogAndroidLogger(DdLogLevel logLevel, float sampleRate, AndroidJavaObject androidLogger) + public DatadogAndroidLogger(DdLogLevel logLevel, float sampleRate, DatadogAndroidPlatform platform, AndroidJavaObject androidLogger) : base(logLevel, sampleRate) { _androidLogger = androidLogger; + _androidPlatform = platform; } public override void AddAttribute(string key, object value) @@ -43,10 +45,31 @@ internal override void PlatformLog(DdLogLevel level, string message, Dictionary< { var androidLevel = InternalHelpers.DdLogLevelToAndroidLogLevel(level); - using var javaAttributes = DatadogAndroidHelpers.DictionaryToJavaMap(attributes); - var errorKind = error?.GetType()?.ToString(); - var errorMessage = error?.Message; - var errorStack = error?.StackTrace?.ToString(); + var javaAttributes = DatadogAndroidHelpers.DictionaryToJavaMap(attributes); + string errorKind = null; + string errorMessage = null; + string errorStack = null; + if (error != null) + { + errorKind = error.GetType()?.ToString(); + errorMessage = error.Message; + var nativeStackTrace = _androidPlatform.GetNativeStack(error); + if (nativeStackTrace != null) + { + var nativeErrorSourceAttributeArgs = AndroidJNIHelper.CreateJNIArgArray( + new object[] + { + new AndroidJavaObject("java.lang.String", DatadogSdk.ConfigKeys.ErrorSourceType), + new AndroidJavaObject("java.lang.String", "ndk+il2cpp"), + }); + AndroidJNI.CallObjectMethod( + javaAttributes.GetRawObject(), + DatadogAndroidHelpers.hashMapPutMethodId, + nativeErrorSourceAttributeArgs); + } + + errorStack = nativeStackTrace ?? error.StackTrace ?? string.Empty; + } _androidLogger.Call("log", (int)androidLevel, message, errorKind, errorMessage, errorStack, javaAttributes); } diff --git a/Runtime/Android/DatadogAndroidPlatform.cs b/Runtime/Android/DatadogAndroidPlatform.cs index 1c3e69c..3c15f3e 100644 --- a/Runtime/Android/DatadogAndroidPlatform.cs +++ b/Runtime/Android/DatadogAndroidPlatform.cs @@ -2,8 +2,10 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-Present Datadog, Inc. +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Text; using Datadog.Unity.Logs; using Datadog.Unity.Rum; using Datadog.Unity.Worker; @@ -35,6 +37,7 @@ public static void InitializeDatadog() internal class DatadogAndroidPlatform : IDatadogPlatform { private AndroidJavaClass _datadogClass; + private bool _shouldTranslateCsStacks = false; public DatadogAndroidPlatform() { @@ -46,6 +49,10 @@ public void Init(DatadogConfigurationOptions options) var applicationId = options.RumApplicationId == string.Empty ? null : options.RumApplicationId; SetVerbosity(options.SdkVerbosity); + // Debug builds have full file / line info and should not be translated, and if you're not outputting symbols + // there will be no way to perform the translation, so avoid it. + _shouldTranslateCsStacks = options.OutputSymbols && options.PerformNativeStackMapping && !Debug.isDebugBuild; + var environment = options.Env; if (environment is null or "") { @@ -61,6 +68,7 @@ public void Init(DatadogConfigurationOptions options) string.Empty, // Variant Name serviceName // Service Name ); + configBuilder.Call("useSite", DatadogConfigurationHelpers.GetSite(options.Site)); configBuilder.Call("setBatchSize", DatadogConfigurationHelpers.GetBatchSize(options.BatchSize)); configBuilder.Call("setUploadFrequency", DatadogConfigurationHelpers.GetUploadFrequency(options.UploadFrequency)); @@ -69,7 +77,8 @@ public void Init(DatadogConfigurationOptions options) var additionalConfig = new Dictionary() { { DatadogSdk.ConfigKeys.Source, "unity" }, - { DatadogSdk.ConfigKeys.SdkVersion, DatadogSdk.SdkVersion } + { DatadogSdk.ConfigKeys.NativeSourceType, "ndk+il2cpp" }, + { DatadogSdk.ConfigKeys.SdkVersion, DatadogSdk.SdkVersion }, }; configBuilder.Call("setAdditionalConfiguration", DatadogAndroidHelpers.DictionaryToJavaMap(additionalConfig)); @@ -120,6 +129,19 @@ public void Init(DatadogConfigurationOptions options) rumConfigBuilder.Call("setVitalsUpdateFrequency", updateFrequency); } + switch (options.TrackNonFatalAnrs) + { + case NonFatalAnrDetectionOption.Disabled: + rumConfigBuilder.Call("trackNonFatalAnrs", false); + break; + case NonFatalAnrDetectionOption.Enabled: + rumConfigBuilder.Call("trackNonFatalAnrs", true); + break; + case NonFatalAnrDetectionOption.SdkDefault: + // Don't do anything. SDK will take care of it. + break; + } + using var internalBuilder = new AndroidJavaClass("com.datadog.android.rum._RumInternalProxy"); using var internalBuilderCompanion = internalBuilder.GetStatic("Companion"); internalBuilderCompanion.Call("setTelemetryConfigurationEventMapper", rumConfigBuilder, new TelemetryCallback()); @@ -190,7 +212,7 @@ public DdLogger CreateLogger(DatadogLoggingOptions options, DatadogWorker worker loggerBuilder.Call("setBundleWithRumEnabled", options.BundleWithRumEnabled); var androidLogger = loggerBuilder.Call("build"); - var innerLogger = new DatadogAndroidLogger(options.RemoteLogThreshold, options.RemoteSampleRate, androidLogger); + var innerLogger = new DatadogAndroidLogger(options.RemoteLogThreshold, options.RemoteSampleRate, this, androidLogger); return new DdWorkerProxyLogger(worker, innerLogger); } @@ -215,7 +237,7 @@ public IDdRum InitRum(DatadogConfigurationOptions options) using var globalRumMonitorClass = new AndroidJavaClass("com.datadog.android.rum.GlobalRumMonitor"); var rum = globalRumMonitorClass.CallStatic("get"); - return new DatadogAndroidRum(rum); + return new DatadogAndroidRum(this, rum); } public void SendDebugTelemetry(string message) @@ -237,6 +259,65 @@ public void ClearAllData() _datadogClass.CallStatic("clearAllData"); } + public string GetNativeStack(Exception error) + { + // Don't perform this action if Datadog wasn't instructed to output symbols + if (!_shouldTranslateCsStacks || error is null) + { + return null; + } + + string resultStack = null; + try + { + if (Il2CppErrorHelper.GetNativeStackFrames( + error, + out IntPtr[] frames, + out string imageUuid, + out string imageName)) + { + if (string.IsNullOrEmpty(imageName)) + { + // Sometimes, Unity returns nothing in the imageName, sometimes it returns the name with + // an extra letter. We'll need to replace this with an actual name / address lookup at some point. + // TODO: RUM-4403 Add support for getting image name from UUID + imageName = "libil2cpp.so"; + } + + // imageName sometimes comes back with an extra letter or symbol at the end, so we need to remove it (bug in Unity?) + if (!imageName.EndsWith(".so")) + { + // Strip off everything after .so. + var soIndex = imageName.IndexOf(".so", StringComparison.Ordinal); + if (soIndex > 0) + { + imageName = imageName.Substring(0, soIndex + 3); + } + } + + // Format of Android Native (NDK) stack trace is: + // + // It can optionally include + at the end but we won't include those. + var stackBuilder = new StringBuilder(); + for (int i = 0; i < frames.Length; ++i) + { + var frame = frames[i].ToInt64(); + + // Currently assuming the frames are all relative. This should also be fixed by RUM-4403 + stackBuilder.AppendLine($"{i} pc {frame:x8} {imageName}"); + } + + resultStack = stackBuilder.ToString(); + } + } + catch(Exception e) + { + SendErrorTelemetry("Failed to get native stack", e.StackTrace, e.GetType().ToString()); + } + + return resultStack; + } + private AndroidJavaObject GetApplicationContext() { using AndroidJavaClass unityPlayer = new("com.unity3d.player.UnityPlayer"); diff --git a/Runtime/Android/DatadogAndroidRum.cs b/Runtime/Android/DatadogAndroidRum.cs index 6fbbf37..cbb04ce 100644 --- a/Runtime/Android/DatadogAndroidRum.cs +++ b/Runtime/Android/DatadogAndroidRum.cs @@ -10,13 +10,15 @@ namespace Datadog.Unity.Android { - public class DatadogAndroidRum : IDdRum + internal class DatadogAndroidRum : IDdRum { private readonly AndroidJavaObject _rum; + private readonly DatadogAndroidPlatform _androidPlatform; - public DatadogAndroidRum(AndroidJavaObject rum) + public DatadogAndroidRum(IDatadogPlatform platform, AndroidJavaObject rum) { _rum = rum; + _androidPlatform = platform as DatadogAndroidPlatform; } public void StartView(string key, string name = null, Dictionary attributes = null) @@ -58,11 +60,30 @@ public void StopAction(RumUserActionType type, string name, Dictionary attributes = null) { - var message = error.Message; - var stack = error.StackTrace; - var javaErrorSource = GetErrorSource(source); + var message = error?.Message; + var stack = error?.StackTrace; + var javaAttributes = DatadogAndroidHelpers.DictionaryToJavaMap(attributes); + if (error != null) + { + var nativeStackTrace = _androidPlatform.GetNativeStack(error); + if (nativeStackTrace != null) + { + stack = nativeStackTrace; + var nativeErrorSourceAttributeArgs = AndroidJNIHelper.CreateJNIArgArray( + new object[] + { + new AndroidJavaObject("java.lang.String", DatadogSdk.ConfigKeys.ErrorSourceType), + new AndroidJavaObject("java.lang.String", "ndk+il2cpp"), + }); + AndroidJNI.CallObjectMethod( + javaAttributes.GetRawObject(), + DatadogAndroidHelpers.hashMapPutMethodId, + nativeErrorSourceAttributeArgs); + } + } + var javaErrorSource = GetErrorSource(source); _rum.Call("addErrorWithStacktrace", message, javaErrorSource, stack, javaAttributes); } diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs index ee350e9..79a92e2 100644 --- a/Runtime/AssemblyInfo.cs +++ b/Runtime/AssemblyInfo.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Runtime.CompilerServices; -[assembly: AssemblyVersion("1.2.0")] +[assembly: AssemblyVersion("1.3.0")] [assembly: InternalsVisibleTo("com.datadoghq.unity.tests")] [assembly: InternalsVisibleTo("com.datadoghq.unity.android")] [assembly: InternalsVisibleTo("com.datadoghq.unity.ios")] diff --git a/Runtime/DatadogConfigurationOptions.cs b/Runtime/DatadogConfigurationOptions.cs index 9a63d05..ba01cb4 100644 --- a/Runtime/DatadogConfigurationOptions.cs +++ b/Runtime/DatadogConfigurationOptions.cs @@ -138,6 +138,31 @@ public enum TrackingConsent Pending, } + /// + /// Options for detecting non-fatal ANRs on Android. The Android SDK can make a decision about whether to track non-fatal + /// ANRs based on the version of Android. + /// + public enum NonFatalAnrDetectionOption + { + /// + /// Use the default behavior for the version of Android. On Android 30+, the default is set to disabled + /// because it would create too much noise over fatal ANRs. On Android 29 and below, however, the + /// reporting of non-fatal ANRs is enabled by default, as fatal ANRs cannot be reported on those versions. + /// + [InspectorName("SDK Default")] + SdkDefault, + + /// + /// Always enable non-fatal ANR tracking, regardless of Android version. + /// + Enabled, + + /// + /// Always disable non-fatal ANR tracking, regardless of Android version. (This is the Unity default) + /// + Disabled, + } + /// /// The frequency at which Datadog samples mobile vitals (FPS, CPU Usage, Memory Usage). /// @@ -194,6 +219,7 @@ public class DatadogConfigurationOptions : ScriptableObject public bool Enabled; public CoreLoggerLevel SdkVerbosity = CoreLoggerLevel.Warn; public bool OutputSymbols; + public bool PerformNativeStackMapping = true; public string ClientToken; public DatadogSite Site; public string Env; @@ -217,6 +243,9 @@ public class DatadogConfigurationOptions : ScriptableObject public float TraceSampleRate = 20.0f; public TraceContextInjection TraceContextInjection = TraceContextInjection.All; public List FirstPartyHosts = new (); + public NonFatalAnrDetectionOption TrackNonFatalAnrs = NonFatalAnrDetectionOption.Disabled; + public bool TrackNonFatalAppHangs = false; + public float NonFatalAppHangThreshold = 0.25f; // Advanced RUM public float TelemetrySampleRate; diff --git a/Runtime/DatadogNoopPlatform.cs b/Runtime/DatadogNoopPlatform.cs index 2faeb20..3f8a1d8 100644 --- a/Runtime/DatadogNoopPlatform.cs +++ b/Runtime/DatadogNoopPlatform.cs @@ -2,6 +2,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-Present Datadog, Inc. +using System; using System.Collections.Generic; using Datadog.Unity.Logs; using Datadog.Unity.Rum; @@ -69,5 +70,10 @@ public void SetTrackingConsent(TrackingConsent trackingConsent) public void ClearAllData() { } + + public string GetNativeStack(Exception error) + { + return string.Empty; + } } } diff --git a/Runtime/DatadogPlatform.cs b/Runtime/DatadogPlatform.cs index e0365c2..c6ac691 100644 --- a/Runtime/DatadogPlatform.cs +++ b/Runtime/DatadogPlatform.cs @@ -2,6 +2,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-Present Datadog, Inc. +using System; using System.Collections.Generic; using Datadog.Unity.Logs; using Datadog.Unity.Rum; @@ -37,6 +38,8 @@ internal interface IDatadogPlatform void SendErrorTelemetry(string message, string stack, string kind); void ClearAllData(); + + string GetNativeStack(Exception error); } /// diff --git a/Runtime/DatadogSdk.cs b/Runtime/DatadogSdk.cs index aad5c3a..68098a2 100644 --- a/Runtime/DatadogSdk.cs +++ b/Runtime/DatadogSdk.cs @@ -60,7 +60,10 @@ public DdLogger DefaultLogger internal IInternalLogger InternalLogger { get { return _internalLogger; } - set { _internalLogger = value; } + set + { + _internalLogger = value ?? new PassThroughInternalLogger(); + } } internal ResourceTrackingHelper ResourceTrackingHelper => _resourceTrackingHelper; @@ -319,8 +322,10 @@ private void OnQuitting() internal class ConfigKeys { internal const string Source = "_dd.source"; + internal const string ErrorSourceType = "_dd.error.source_type"; internal const string BuildId = "_dd.build_id"; internal const string SdkVersion = "_dd.sdk_version"; + internal const string NativeSourceType = "_dd.native_source_type"; } } } diff --git a/Runtime/DdSdkProcessor.cs b/Runtime/DdSdkProcessor.cs index 9ac5b2d..3672275 100644 --- a/Runtime/DdSdkProcessor.cs +++ b/Runtime/DdSdkProcessor.cs @@ -85,7 +85,7 @@ public void Discard() // TODO: This should be moved from the core_sdk as it actually applies to Logs. internal class AddGlobalAttributesMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddGlobalAttributesMessage(), actionOnRelease: (obj) => obj.Reset()); private AddGlobalAttributesMessage() @@ -116,7 +116,7 @@ private void Reset() internal class RemoveGlobalAttributeMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new RemoveGlobalAttributeMessage(), actionOnRelease: (obj) => obj.Reset()); private RemoveGlobalAttributeMessage() diff --git a/Runtime/Il2CppErrorHelper.cs b/Runtime/Il2CppErrorHelper.cs new file mode 100644 index 0000000..e0df39a --- /dev/null +++ b/Runtime/Il2CppErrorHelper.cs @@ -0,0 +1,122 @@ +using System; +using System.Runtime.InteropServices; +using Datadog.Unity.Logs; + +namespace Datadog.Unity +{ + internal class Il2CppErrorHelper + { + /// + /// Convert an exception to a native stack trace. If the stack trace cannot be converted, + /// this returns exception.StackTrace.ToString(). + /// + /// The C# exception. + /// Out parameter containing the stack frames, or null if frames failed to fetch. + /// Out parameter containing the image UUID, or null if frames failed to fetch. + /// Out parameter containing the image name. May be null or empty. + public static bool GetNativeStackFrames(Exception exception, out IntPtr[] frames, out string imageUuid, out string imageName) + { + var gchandle = GCHandle.Alloc(exception); + var addresses = IntPtr.Zero; + + try + { + var handlePtr = GCHandle.ToIntPtr(gchandle); + var targetAddress = GcHandleGetTarget(handlePtr); + + var numFrames = 0; + imageUuid = null; + imageName = null; + GetStackFrames(targetAddress, out addresses, out numFrames, out imageUuid, out imageName); + frames = new IntPtr[numFrames]; + Marshal.Copy(addresses, frames, 0, numFrames); + if (frames[0] == IntPtr.Zero) + { + // First frame is null, this is likely a development build + return false; + } + } + catch (Exception e) + { + // TODO: Internal logger + imageUuid = null; + imageName = null; + frames = null; + return false; + } + finally + { + gchandle.Free(); + if (addresses != IntPtr.Zero) + { + il2cpp_free(addresses); + } + } + + return true; + } + + private static void GetStackFrames(IntPtr exc, out IntPtr addresses, out int numFrames, out string imageUuid, + out string imageName) + { + var imageUuidPtr = IntPtr.Zero; + var imageNamePtr = IntPtr.Zero; + try + { +#if UNITY_2021_3_OR_NEWER + il2cpp_native_stack_trace(exc, out addresses, out numFrames, out imageUuidPtr, out imageNamePtr); + imageName = (imageNamePtr == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(imageNamePtr); +#else + // Method expects a pre-allocated buffer for the UUID. Max length is 40 chars + imageUuidPtr = il2cpp_alloc(41); + il2cpp_native_stack_trace(exc, out addresses, out numFrames, imageUuidPtr); + imageName = null; +#endif + imageUuid = (imageUuidPtr == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(imageUuidPtr); + } + finally + { + il2cpp_free(imageUuidPtr); + il2cpp_free(imageNamePtr); + } + } + + #region Unity C Interface + + private static IntPtr GcHandleGetTarget(IntPtr gchandle) + { + #if UNITY_2023 + return il2cpp_gchandle_get_target(gchandle); + #else + return il2cpp_gchandle_get_target(gchandle.ToInt32()); + #endif + } + +#if UNITY_2023 + [DllImport("__Internal")] + private static extern IntPtr il2cpp_gchandle_get_target(IntPtr gchandle); + +#else // Pre Unity 2023 + [DllImport("__Internal")] + private static extern IntPtr il2cpp_gchandle_get_target(int gchandle); + #endif + +#if UNITY_2021_3_OR_NEWER + [DllImport("__Internal")] + private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName); + +#else + [DllImport("__Internal")] + private static extern IntPtr il2cpp_alloc(uint size); + + [DllImport("__Internal")] + private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, IntPtr imageUUID); +#endif + + [DllImport("__Internal")] + private static extern void il2cpp_free(IntPtr ptr); + + #endregion + } + +} diff --git a/Runtime/Il2CppErrorHelper.cs.meta b/Runtime/Il2CppErrorHelper.cs.meta new file mode 100644 index 0000000..ab0dd46 --- /dev/null +++ b/Runtime/Il2CppErrorHelper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3955a1e2ad524671bf44931178bed8d2 +timeCreated: 1712256237 \ No newline at end of file diff --git a/Runtime/InternalLogger.cs b/Runtime/InternalLogger.cs index b46fdeb..042c821 100644 --- a/Runtime/InternalLogger.cs +++ b/Runtime/InternalLogger.cs @@ -105,7 +105,7 @@ public void Process(IDatadogWorkerMessage message) internal class TelemetryDebugMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new TelemetryDebugMessage(), actionOnRelease: (obj) => obj.Reset()); private TelemetryDebugMessage() @@ -138,7 +138,7 @@ private void Reset() internal class TelemetryErrorMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new TelemetryErrorMessage(), actionOnRelease: (obj) => obj.Reset()); private TelemetryErrorMessage() diff --git a/Runtime/Logs/DatadogLoggingOptions.cs b/Runtime/Logs/DatadogLoggingOptions.cs index 735b158..e395d76 100644 --- a/Runtime/Logs/DatadogLoggingOptions.cs +++ b/Runtime/Logs/DatadogLoggingOptions.cs @@ -3,6 +3,7 @@ // Copyright 2023-Present Datadog, Inc. using System.Diagnostics.CodeAnalysis; +using UnityEngine.Scripting; namespace Datadog.Unity.Logs { @@ -10,16 +11,22 @@ namespace Datadog.Unity.Logs public class DatadogLoggingOptions { + [Preserve] public string Service = null; + [Preserve] public string Name = null; + [Preserve] public bool NetworkInfoEnabled = false; + [Preserve] public bool BundleWithRumEnabled = true; + [Preserve] public float RemoteSampleRate = 100.0f; + [Preserve] public DdLogLevel RemoteLogThreshold = DdLogLevel.Debug; } } diff --git a/Runtime/Logs/DdLogProcessor.cs b/Runtime/Logs/DdLogProcessor.cs index 223f11c..769f41c 100644 --- a/Runtime/Logs/DdLogProcessor.cs +++ b/Runtime/Logs/DdLogProcessor.cs @@ -43,7 +43,7 @@ public void Process(IDatadogWorkerMessage message) internal class LogMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new LogMessage(), actionOnRelease: (obj) => obj.Reset()); private LogMessage() @@ -89,7 +89,7 @@ private void Reset() internal class AddTagMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddTagMessage(), actionOnRelease: (obj) => obj.Reset()); private AddTagMessage() @@ -128,7 +128,7 @@ private void Reset() internal class RemoveTagMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new RemoveTagMessage(), actionOnRelease: (obj) => obj.Reset()); private RemoveTagMessage() @@ -163,7 +163,7 @@ private void Reset() internal class RemoveTagsWithKeyMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new RemoveTagsWithKeyMessage(), actionOnRelease: (obj) => obj.Reset()); private RemoveTagsWithKeyMessage() @@ -198,7 +198,7 @@ private void Reset() internal class AddAttributeMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddAttributeMessage(), actionOnRelease: (obj) => obj.Reset()); private AddAttributeMessage() @@ -237,7 +237,7 @@ private void Reset() internal class RemoveAttributeMessage : IDatadogWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new RemoveAttributeMessage(), actionOnRelease: (obj) => obj.Reset()); private RemoveAttributeMessage() diff --git a/Runtime/Rum/DatadogTrackedWebRequest.cs b/Runtime/Rum/DatadogTrackedWebRequest.cs index b3e68a9..7c13cd6 100644 --- a/Runtime/Rum/DatadogTrackedWebRequest.cs +++ b/Runtime/Rum/DatadogTrackedWebRequest.cs @@ -228,6 +228,12 @@ public UnityWebRequestAsyncOperation SendWebRequest() break; } } + catch (NullReferenceException e) + { + // This webrequest was disposed before the operation completed. This is not a telemetry + // error, but we should stop the resource. + DatadogSdk.Instance.Rum.StopResourceWithError(rumKey, "RequestDisposed", error); + } catch (Exception e) { DatadogSdk.Instance.InternalLogger?.TelemetryError("Error stopping RUM resource.", e); diff --git a/Runtime/Rum/DdRumProcessor.cs b/Runtime/Rum/DdRumProcessor.cs index f4cf695..81ddf38 100644 --- a/Runtime/Rum/DdRumProcessor.cs +++ b/Runtime/Rum/DdRumProcessor.cs @@ -101,7 +101,7 @@ internal abstract class DdRumWorkerMessage : IDatadogWorkerMessage internal class StartViewMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StartViewMessage(), actionOnRelease: (obj) => obj.Reset()); private StartViewMessage() @@ -141,7 +141,7 @@ private void Reset() internal class StopViewMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StopViewMessage(), actionOnRelease: (obj) => obj.Reset()); private StopViewMessage() @@ -177,7 +177,7 @@ private void Reset() internal class AddUserActionMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddUserActionMessage(), actionOnRelease: (obj) => obj.Reset()); private AddUserActionMessage() @@ -216,7 +216,7 @@ private void Reset() internal class StartUserActionMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StartUserActionMessage(), actionOnRelease: (obj) => obj.Reset()); private StartUserActionMessage() @@ -255,7 +255,7 @@ private void Reset() internal class StopUserActionMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StopUserActionMessage(), actionOnRelease: (obj) => obj.Reset()); private StopUserActionMessage() @@ -294,7 +294,7 @@ private void Reset() internal class AddErrorMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddErrorMessage(), actionOnRelease: (obj) => obj.Reset()); private AddErrorMessage() @@ -334,7 +334,7 @@ private void Reset() internal class AddAttributeMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddAttributeMessage(), actionOnRelease: (obj) => obj.Reset()); private AddAttributeMessage() @@ -368,7 +368,7 @@ private void Reset() internal class RemoveAttributeMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new (createFunc: () => new RemoveAttributeMessage()); + private static readonly ThreadSafeObjectPool _pool = new (createFunc: () => new RemoveAttributeMessage()); private RemoveAttributeMessage() { @@ -392,7 +392,7 @@ public override void Discard() internal class StartResourceLoadingMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StartResourceLoadingMessage(), actionOnRelease: (obj) => obj.Reset()); private StartResourceLoadingMessage() @@ -435,7 +435,7 @@ private void Reset() internal class StopResourceLoadingMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StopResourceLoadingMessage(), actionOnRelease: (obj) => obj.Reset()); private StopResourceLoadingMessage() @@ -482,7 +482,7 @@ private void Reset() internal class StopResourceLoadingWithErrorMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new StopResourceLoadingWithErrorMessage(), actionOnRelease: (obj) => obj.Reset()); private StopResourceLoadingWithErrorMessage() @@ -529,7 +529,7 @@ private void Reset() internal class AddFeatureFlagEvaluationMessage : DdRumWorkerMessage { - private static ObjectPool _pool = new ( + private static readonly ThreadSafeObjectPool _pool = new ( createFunc: () => new AddFeatureFlagEvaluationMessage(), actionOnRelease: (obj) => obj.Reset()); private AddFeatureFlagEvaluationMessage() diff --git a/Runtime/Worker/ThreadSafeObjectPool.cs b/Runtime/Worker/ThreadSafeObjectPool.cs new file mode 100644 index 0000000..3a34d28 --- /dev/null +++ b/Runtime/Worker/ThreadSafeObjectPool.cs @@ -0,0 +1,74 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024-Present Datadog, Inc. + +using System; +using UnityEngine.Pool; + +namespace Datadog.Unity.Worker +{ + public class ThreadSafeObjectPool : IDisposable, IObjectPool + where T : class + { + private readonly object _lock = new (); + private readonly ObjectPool _pool; + + public ThreadSafeObjectPool( + Func createFunc, + Action actionOnGet = null, + Action actionOnRelease = null, + Action actionOnDestroy = null, + bool collectionCheck = true, + int defaultCapacity = 10, + int maxSize = 10000) + { + _pool = new (createFunc, actionOnGet, actionOnRelease, actionOnDestroy, collectionCheck, defaultCapacity, + maxSize); + } + + public int CountInactive + { + get + { + lock(_lock) + { + return _pool.CountInactive; + } + } + } + + public T Get() + { + lock (_lock) + { + return _pool.Get(); + } + } + + public PooledObject Get(out T v) + { + lock (_lock) + { + return _pool.Get(out v); + } + } + + public void Release(T element) + { + lock (_lock) + { + _pool.Release(element); + } + } + + public void Clear() + { + lock (_lock) + { + _pool.Clear(); + } + } + + public void Dispose() => this.Clear(); + } +} diff --git a/Runtime/Worker/ThreadSafeObjectPool.cs.meta b/Runtime/Worker/ThreadSafeObjectPool.cs.meta new file mode 100644 index 0000000..64aba83 --- /dev/null +++ b/Runtime/Worker/ThreadSafeObjectPool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: eda198ca94714446a16fc3653cc87519 +timeCreated: 1729006656 \ No newline at end of file diff --git a/Runtime/iOS/DatadogiOSLogger.cs b/Runtime/iOS/DatadogiOSLogger.cs index d96e197..9e6192a 100644 --- a/Runtime/iOS/DatadogiOSLogger.cs +++ b/Runtime/iOS/DatadogiOSLogger.cs @@ -14,20 +14,22 @@ namespace Datadog.Unity.iOS public class DatadogiOSLogger : DdLogger { private readonly string _loggerId; + private readonly IDatadogPlatform _platform; - private DatadogiOSLogger(DdLogLevel logLevel, float sampleRate, string loggerId) + private DatadogiOSLogger(DatadogiOSPlatform platform, DdLogLevel logLevel, float sampleRate, string loggerId) : base(logLevel, sampleRate) { _loggerId = loggerId; + _platform = platform; } - public static DatadogiOSLogger Create(DatadogLoggingOptions options) + internal static DatadogiOSLogger Create(DatadogiOSPlatform platform, DatadogLoggingOptions options) { var jsonOptions = JsonConvert.SerializeObject(options); var loggerId = DatadogLoggingBridge.DatadogLogging_CreateLogger(jsonOptions); if (loggerId != null) { - return new DatadogiOSLogger(options.RemoteLogThreshold, options.RemoteSampleRate, loggerId); + return new DatadogiOSLogger(platform, options.RemoteLogThreshold, options.RemoteSampleRate, loggerId); } return null; @@ -35,21 +37,31 @@ public static DatadogiOSLogger Create(DatadogLoggingOptions options) internal override void PlatformLog(DdLogLevel level, string message, Dictionary attributes = null, Exception error = null) { - // To serialize a non-object, we need to use JsonConvert, which isn't as optimized but supports - // Dictionaries, where JsonUtility does not. - var jsonAttributes = JsonConvert.SerializeObject(attributes); string jsonError = null; if (error != null) { + var nativeStackTrace = _platform.GetNativeStack(error); var errorInfo = new Dictionary() { { "type", error.GetType()?.ToString() ?? string.Empty }, { "message", error.Message ?? string.Empty }, - { "stackTrace", error.StackTrace?.ToString() ?? string.Empty }, + { "stackTrace", nativeStackTrace ?? error.StackTrace ?? string.Empty }, }; + + if (nativeStackTrace != null) + { + attributes = attributes != null ? new (attributes) : new(); + attributes["_dd.error.include_binary_images"] = true; + attributes[DatadogSdk.ConfigKeys.ErrorSourceType] = "ios+il2cpp"; + } + jsonError = JsonConvert.SerializeObject(errorInfo); } + // To serialize a non-object, we need to use JsonConvert, which isn't as optimized but supports + // Dictionaries, where JsonUtility does not. + var jsonAttributes = JsonConvert.SerializeObject(attributes); + DatadogLoggingBridge.DatadogLogging_Log(_loggerId, (int)level, message, jsonAttributes, jsonError); } diff --git a/Runtime/iOS/DatadogiOSPlatform.cs b/Runtime/iOS/DatadogiOSPlatform.cs index 4ddf151..b371d7c 100644 --- a/Runtime/iOS/DatadogiOSPlatform.cs +++ b/Runtime/iOS/DatadogiOSPlatform.cs @@ -1,9 +1,12 @@ // Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2023-Present Datadog, Inc. + +using System; using System.Collections.Generic; +using System.IO; using System.Runtime.InteropServices; -using Datadog.Unity; +using System.Text; using Datadog.Unity.Logs; using Datadog.Unity.Rum; using Datadog.Unity.Worker; @@ -34,9 +37,16 @@ public static void InitializeDatadog() internal class DatadogiOSPlatform : IDatadogPlatform { + private Dictionary _moduleLoadAddresses = new Dictionary(); + private bool _shouldTranslateCsStacks = false; + public void Init(DatadogConfigurationOptions options) { Datadog_UpdateTelemetryConfiguration(Application.unityVersion); + + // Debug builds have full file / line info and should not be translated, and if you're not outputting symbols + // there will be no way to perform the translation, so avoid it. + _shouldTranslateCsStacks = options.OutputSymbols && options.PerformNativeStackMapping && !Debug.isDebugBuild; } public void SetVerbosity(CoreLoggerLevel logLevel) @@ -69,7 +79,7 @@ public void SetTrackingConsent(TrackingConsent trackingConsent) public DdLogger CreateLogger(DatadogLoggingOptions options, DatadogWorker worker) { - var innerLogger = DatadogiOSLogger.Create(options); + var innerLogger = DatadogiOSLogger.Create(this, options); return new DdWorkerProxyLogger(worker, innerLogger); } @@ -98,7 +108,7 @@ public void RemoveLogsAttribute(string key) public IDdRum InitRum(DatadogConfigurationOptions options) { - return new DatadogiOSRum(); + return new DatadogiOSRum(this); } public void SendDebugTelemetry(string message) @@ -116,6 +126,88 @@ public void ClearAllData() Datadog_ClearAllData(); } + public string GetNativeStack(Exception error) + { + // Don't perform this action if Datadog wasn't instructed to output symbols + if (!_shouldTranslateCsStacks || error is null) + { + return null; + } + + string resultStack = null; + try + { + if (Il2CppErrorHelper.GetNativeStackFrames( + error, + out IntPtr[] frames, + out string imageUuid, + out string imageName)) + { + var imageLoadAddress = 0L; + if (_moduleLoadAddresses.ContainsKey(imageUuid)) + { + imageLoadAddress = _moduleLoadAddresses[imageUuid]; + } + else + { + // Reformat image UUID so it can be parsed by the native code + var standardImageUuid = ReformatUuid(imageUuid); + imageLoadAddress = Datadog_FindImageLoadAddress(standardImageUuid); + if (imageLoadAddress > 0) + { + _moduleLoadAddresses[imageUuid] = imageLoadAddress; + } + } + + if (imageLoadAddress <= 0) + { + // Couldn't find the image load address, so we can't supply the stack trace + return null; + } + + var moduleName = Path.GetFileNameWithoutExtension(imageName); + // Strip off weird \u0001 character that appears at the end of module names + moduleName = moduleName.Replace("\u0001", string.Empty); + + + // Format of iOS Native stack trace is: + // + + // Addresses are in hex, but offset is in decimal. + StringBuilder stackBuilder = new StringBuilder(); + for (int i = 0; i < frames.Length; i++) + { + var frame = frames[i].ToInt64(); + var isAbsolute = frame > imageLoadAddress; + + // TODO: Check to see if we get absolute addresses outside of the supplied image + var absoluteAddress = isAbsolute ? frame : frame + imageLoadAddress; + var offset = absoluteAddress - imageLoadAddress; + stackBuilder.Append( + $"{i,-3} {moduleName,-32} 0x{absoluteAddress:x16} 0x{imageLoadAddress:x8} + {offset}\n"); + } + + + resultStack = stackBuilder.ToString(); + } + } + catch (Exception e) + { + SendErrorTelemetry("Failed to get native stack", e.StackTrace, e.GetType().ToString()); + } + + return resultStack; + } + + private static string ReformatUuid(string imageUuid) + { + var sb = new StringBuilder(imageUuid); + sb.Insert(8, '-'); + sb.Insert(13, '-'); + sb.Insert(18, '-'); + sb.Insert(23, '-'); + return sb.ToString(); + } + [DllImport("__Internal")] private static extern void Datadog_SetSdkVerbosity(int logLevel); @@ -145,5 +237,8 @@ public void ClearAllData() [DllImport("__Internal")] private static extern void Datadog_UpdateTelemetryConfiguration(string unityVersion); + + [DllImport("__Internal")] + private static extern long Datadog_FindImageLoadAddress(string uuid); } } diff --git a/Runtime/iOS/DatadogiOSRum.cs b/Runtime/iOS/DatadogiOSRum.cs index f4ff909..0b1664a 100644 --- a/Runtime/iOS/DatadogiOSRum.cs +++ b/Runtime/iOS/DatadogiOSRum.cs @@ -12,6 +12,13 @@ namespace Datadog.Unity.iOS { internal class DatadogiOSRum : IDdRum { + private readonly DatadogiOSPlatform _platform; + + public DatadogiOSRum(DatadogiOSPlatform platform) + { + _platform = platform; + } + public void StartView(string key, string name = null, Dictionary attributes = null) { attributes ??= new Dictionary(); @@ -54,12 +61,29 @@ public void StopAction(RumUserActionType type, string name, Dictionary attributes = null) { + string stackTrace = null; + if (error != null) + { + var nativeStackTrace = _platform.GetNativeStack(error); + if (nativeStackTrace != null) + { + attributes = attributes == null ? new () : new (attributes); + attributes["_dd.error.include_binary_images"] = true; + attributes[DatadogSdk.ConfigKeys.ErrorSourceType] = "ios+il2cpp"; + stackTrace = nativeStackTrace; + } + else + { + stackTrace = error?.StackTrace ?? string.Empty; + } + } + attributes ??= new Dictionary(); + var jsonAttributes = JsonConvert.SerializeObject(attributes); var errorType = error?.GetType()?.ToString(); var errorMessage = error?.Message; - var stackTrace = error?.StackTrace; DatadogRumBridge.DatadogRum_AddError(errorMessage, source.ToString(), errorType, stackTrace, jsonAttributes); } diff --git a/package.json b/package.json index f4454f7..8df9ae2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.datadoghq.unity", - "version": "1.2.0", + "version": "1.3.0", "displayName": "Datadog Unity", "description": "Datadog Plugin for Unity", "unity": "2022.3",