sentry-dotnet icon indicating copy to clipboard operation
sentry-dotnet copied to clipboard

Adding support for generating or passing UUID when uploading progurad mapping on .NET MAUI

Open rafalka opened this issue 11 months ago • 9 comments

Problem Statement

Uploading a progurad file by <SentryUploadAndroidProguardMapping> but this file is not associated with any release

According https://docs.sentry.io/cli/dif/#uploading-files this association requires adding io.sentry.proguard-uuid meetadata to and using sentry-cli upload-proguard --uuid ...

Solution Brainstorm

Preferred solution: Generate UUID, inject it to AndroidManifest.xml and use during uploading or: read this UUID from AndroidManifest.xml and use during uploading or: add support for an environment variable

BTW: Shouldn't other switches --app-id my.app.id, --version. --version-code be used as well?

rafalka avatar Jan 07 '25 14:01 rafalka

Checks out, without uuid the there's no way for the mapping file and the app release to be associated.

Here's we add it to the gradle plugin:

https://github.com/getsentry/sentry-android-gradle-plugin/blob/3400d0b301a068815e73d240391043768a1c32ed/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadProguardMappingsTask.kt#L67-L68

The app also needs to know what the uuid was, so it can attach it to events. The server needs to know what mapping file to look up.

like: https://github.com/getsentry/sentry-android-gradle-plugin/blob/3400d0b301a068815e73d240391043768a1c32ed/plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryGenerateProguardUuidTask.kt#L54C19-L54C56

Ideally we use meta-data, like the docs you linked say:

<application>
  <meta-data
    android:name="io.sentry.proguard-uuid"
    android:value="A_VALID_UUID"
  />
</application>

bruno-garcia avatar Jan 07 '25 23:01 bruno-garcia

I managed to find a workaround until this bug will be fixed:

    <PropertyGroup  Condition="'$(Configuration)'=='Release'">
        <ProguardUuid>$([System.Guid]::NewGuid())</ProguardUuid>
    </PropertyGroup>
    <ItemGroup  Condition="'$(Configuration)'=='Release'">
        <AssemblyAttribute Include="Android.App.MetaData">
            <_Parameter1>"io.sentry.proguard-uuid", Value = "$(ProguardUuid)"</_Parameter1>
            <_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
        </AssemblyAttribute>
    </ItemGroup>

    <!-- Upload Android Proguard mapping file to Sentry after the build. -->
    <Target Name="CustomUploadAndroidProguardMappingFileToSentry" AfterTargets="UploadDebugInfoToSentry" DependsOnTargets="PrepareSentryCLI"
        Condition="'$(SentryCLI)' != '' And '$(AndroidProguardMappingFile)' != '' And '$(Configuration)'=='Release'">

        <Message Importance="High" Text="Preparing to upload Android Proguard mapping to Sentry for '$(MSBuildProjectName)': $(AndroidProguardMappingFile))" />

        <Exec Command="$(SentryCLIProGuardMappingUploadCommand) --uuid &quot;$(ProguardUuid)&quot; --app-id &quot;$(ApplicationId)&quot; --version &quot;$(ApplicationDisplayVersion)&quot; --version-code &quot;$(ApplicationVersion)&quot; $(AndroidProguardMappingFile)" IgnoreExitCode="true" ContinueOnError="WarnAndContinue">
            <Output TaskParameter="ExitCode" PropertyName="_SentryCLIExitCode" />
        </Exec>
        <Warning Condition="'$(_SentryCLIExitCode)' != '0'" Text="Sentry CLI could not upload proguard mapping." />
    </Target>    
    

please note than I use separated platform project and these entries I added only to my Android projects. For single project MAUI app some extra conditions may be required

rafalka avatar Jan 08 '25 08:01 rafalka

Thank you for sharing that! would you be willing to share that fix in a PR? We have a single file where we do the sentry cli commands. it just missed these its u figured out already.

bruno-garcia avatar Jan 08 '25 12:01 bruno-garcia

Sure! Feel free to use it.

rafalka avatar Jan 08 '25 15:01 rafalka

Thanks for the code @rafalka I've made my own version of your code, digging into how things work and understanding it properly.

If using VS I highly recommend MSBuild Editor extension to effectively navigate and dig into any .targets / .props files. I use it to discover where MSBuild variables are defined, and places they are consumed.

Notes about the code

  • in theory SentryCLIUploadOptions should have been enough, not needing the Target code. But in Sentry.targets it is applied to both SentryCLIProGuardMappingUploadCommand and SentryCLIDebugFilesUploadCommand
  • the definition of SentryCLIUploadOptions2 is my thing, only applying it to SentryCLIProGuardMappingUploadCommand
  • I'm unsure parameters --app-id / --version / --version-code are really needed in SentryCLIProGuardMappingUploadCommand

The code comments describe how things are tied together from build to embedding in app, and finally adding the UUID to events sent to Sentry.

<!-- ISSUE: Adding support for generating or passing UUID when uploading progurad mapping on .NET MAUI -->
<!-- https://github.com/getsentry/sentry-dotnet/issues/3872 -->
<PropertyGroup Condition="'$(Configuration)'=='Release' AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
    <ProguardUuid>$([System.Guid]::NewGuid())</ProguardUuid>
    <SentryCLIUploadOptions2>--uuid &quot;$(ProguardUuid)&quot; --app-id &quot;$(ApplicationId)&quot; --version &quot;$(ApplicationDisplayVersion)&quot; --version-code &quot;$(ApplicationVersion)&quot;</SentryCLIUploadOptions2>
</PropertyGroup>
<ItemGroup Condition="'$(Configuration)'=='Release' AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
    <!-- [MSBuild] write .NET assembly attribute Android.App.MetaDataAttribute -->
    <!-- https://learn.microsoft.com/en-us/dotnet/api/android.app.metadataattribute -->
    <!-- [MSBuild] which will be copied to Android manifest -->
    <!-- https://github.com/dotnet/android/blob/e6e83daebe7e7d0126329a49ac3c615914eeed59/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs#L587 -->
    <!-- https://learn.microsoft.com/en-us/dotnet/maui/android/manifest -->
    <!-- [Sentry Java SDK] manifest read by Sentry and added to events sent by app -->
    <!-- https://github.com/getsentry/sentry-java/blob/8696c895261a3f15fd8146f5829851cc701530a5/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java#L78 -->
    <!-- NOTE: as of Sentry 5.7.0 (NuGet) proguard UUID are not added to SentryExent debug_meta/images  -->
    <!-- https://develop.sentry.dev/sdk/data-model/event-payloads/debugmeta/#proguard-images -->
    <AssemblyAttribute Include="Android.App.MetaData">
        <_Parameter1>"io.sentry.proguard-uuid", Value = "$(ProguardUuid)"</_Parameter1>
        <_Parameter1_IsLiteral>true</_Parameter1_IsLiteral>
    </AssemblyAttribute>
</ItemGroup>
<Target Name="AppendSentryCLIProGuardMappingUploadCommand" BeforeTargets="UploadAndroidProguardMappingFileToSentry" DependsOnTargets="PrepareSentryCLI">
    <PropertyGroup>
        <SentryCLIProGuardMappingUploadCommand Condition="'$(SentryCLIUploadOptions2.Trim())' != ''">$(SentryCLIProGuardMappingUploadCommand) $(SentryCLIUploadOptions2.Trim())</SentryCLIProGuardMappingUploadCommand>
    </PropertyGroup>
</Target>

espenrl avatar May 16 '25 11:05 espenrl

With the above solution I get the build time generated UUID embedded into the Android manifest, as well as associated with proguard mappings uploaded to Sentry. All good.

I tested it using Sentry.Maui v5.7.0 and discovered that it partially works. Events stemming from the native SDK contains the UUID. But not the ones stemming from the .NET side of things.

I can't make a workaround because the .NET definition of DebugImage for debug_meta.images doesn't have a property for uuid. And the class is also sealed.

An event should look like this

Image

espenrl avatar May 16 '25 11:05 espenrl

I didn't dig into the details yet so only commenting on:

I can't make a workaround because the .NET definition of DebugImage for debug_meta.images doesn't have a property for uuid. And the class is also sealed.

we can add properties if that's needed btw, but not sure if this is really needed since the Android SDK under the hood should take care of adding proguard information to events with ninified Java code. But from C# is this needed?

Also note that looking at json events after processing (downloaded from sentry) they will contain more metadata which gets added while processing the event

I tested it using Sentry.Maui v5.7.0 and discovered that it partially works. Events stemming from the native SDK contains the UUID. But not the ones stemming from the .NET side of things.

Is this needed in C# events? there's no minified Java in those, are there? if so, I'm not even sure just adding the IDs will fix it. Some examples/reports would be helpful to look into

bruno-garcia avatar May 16 '25 12:05 bruno-garcia

Honestly I have no idea if the proguard mappings come into play with regards to .NET code. I was assuming AOT assemblies was processed. But it is perhaps, as you mentioned, restricted to Java bytecode.

Another thought. What about calls into Java libraries by the .NET runtime - having stacktraces that contains both Java and .NET functions. How can those stacktraces be symbolicated correctly without the proguard mappings? Food for thought. I'll have to dig deeper myself.

espenrl avatar May 16 '25 18:05 espenrl

Another thought. What about calls into Java libraries by the .NET runtime - having stacktraces that contains both Java and .NET functions. How can those stacktraces be symbolicated correctly without the proguard mappings? Food for thought. I'll have to dig deeper myself.

If we're calling these via JNI, I believe proguard can't be used on those members since their name is written on the .NET side and if they got renamed by proguard, code would fail at runtime.

So I believe code that gets affected by proguard is in practice not accessed via C#. I could be wrong and proguard somehow runs and the output of the mappings is used by .NET to update its JNI calls and call into the new/minified code. But I'm unaware if that's the case.

I'd expect in such case for us to get some reports of stack traces in Sentry showing minified Java code (Perhaps interleaved with C# code) but I haven't seen such report yet.

All of that said, having the proguard uuid in the manifest (or sentry.properties) is still needed so that any Java error that happens to run minified code gets properly unminified/symbolicated. So does make sense to make sure we get the uuid added to the manifest metadata.

bruno-garcia avatar May 17 '25 18:05 bruno-garcia