Raygun4Net update: Portable PDBs and offline storage
Posted Jun 26, 2024 | 6 min. (1179 words)We’re excited to roll out support for Portable PDBs and offline error storage in Raygun4Net 11.0.0. In this article, we’ll break down these features and how they can enhance your error identification and debugging process.
Ever seen an error in Raygun but missing the file and line number in the stack trace? This happens when debug symbols aren’t available at runtime. With Portable PDB support, this issue is resolved. Just upload your symbols to Raygun, and we’ll enrich your stack traces with these missing details.
We’ve introduced offline storage for crash reports, ideal for devices with unreliable connectivity. Set up an offline store, and if a device can’t connect to Raygun servers, crashes are saved locally. Once back online, the stored crashes are sent to Raygun, ensuring no loss of critical application insights.
Ready to see how these features can streamline your debugging process and improve the reliability of your applications? Let’s get started.
Portable PDBs
Portable PDBs (Program Database Files) are essential for modern .NET applications, providing a lightweight, cross-platform debugging solution. They contain crucial debugging information, allowing you to accurately trace code execution. Portable PDB support is built into Raygun4Net 11.0.0 and above. Just update your NuGet reference, and you’ll get all the benefits with zero code changes.
Capturing additional stack trace information
Raygun4Net has always provided stack trace insights, but without debugging symbols at runtime, those traces lacked file and line numbers. To fix this, we’ve added a new feature that lets us determine the file and line number even without runtime debugging symbols.
We do this by capturing the ILOffset and MethodToken along with the associated PDB when an error occurs. With this extra information and a copy of the PDB uploaded to Raygun, we can locate the debugging symbols and reverse the stack trace to populate the file and line numbers for each stack frame.
For .NET MAUI and Android, we had to take a slightly different approach. Check out the “extras for experts” section at the end of this blog.
Before:
After:
Uploading PDBs to Raygun
You can upload PDBs to Raygun through our Symbol Center or programmatically using the API. This allows Raygun to access the necessary debugging symbols for detailed stack traces. Here’s how you can upload PDB files programmatically:
curl -L
-H "Authorization: Basic <BASIC_AUTH_TOKEN>"
-F "SymbolFile=@<PATH_TO_PDB_FILE>"
https://app.raygun.com/upload/pdbsymbols/<RAYGUN_APPLICATION_ID>
For more details on the API, you can visit our documentation.
Alternatively, visit the Symbol Center under Application Settings in Raygun to manually upload PDB files.
Offline storage
Offline storage is crucial for applications that may face intermittent connectivity issues. When a device is offline and can’t reach Raygun, this feature ensures that all error and performance data is captured and stored locally. Once the device reconnects, the stored data is automatically sent to Raygun, ensuring a complete and accurate record of all events.
An exception is stored offline when:
- There’s a network connectivity issue (like no internet on a mobile device)
- The Raygun API responds with an HTTP 5xx error, indicating a unexpected server issue
Getting started
By specifying an OfflineStore when constructing the client, this signals to the RaygunClient that the offline store is available. All the sending, storing and resending of crash reports is automatically handled for you by the RaygunClient and OfflineStore
var backgroundSendStrategy = new TimerBasedSendStrategy(TimeSpan.FromSeconds(30));
var offlineStore = new LocalApplicationDataCrashReportStore(backgroundSendStrategy);
var raygun = new RaygunClient(new RaygunSettings
{
ApiKey = "your_api_key_here",
OfflineStore = offlineStore
});
You can also extend and create your own custom implementations of OfflineStoreBase and IBackgroundSendStrategy to further customize where errors are stored and when they are sent. For example, you might not want a timer-based trigger for resending, so you could create another implementation that checks that an internet connection is available, and signals the store to send any outstanding crashes to Raygun.
Extras for experts - reversing the .NET MAUI Android distributable
Like all things in software engineering, it’s never as simple as you first thought. When we tested the first implementation of Portable PDB support on Android, we hit a bunch of issues. After some investigation, we found out that Microsoft had improved how executables and assemblies are loaded into the .NET runtime for Android. This new method is called Assembly Stores, and you can check out the official Microsoft blog for more details.
Long story short, Android file loading was causing performance issues in Xamarin/.NET MAUI apps, directly impacting startup times. To fix this, Microsoft engineers merged all assemblies into a big binary blob and created a manifest file to read them at runtime. This boosted performance significantly, but it made accessing assemblies and the associated debugging information, a must for our Portable PDB support, a real headache.
Thankfully, the Xamarin/.NET MAUI team provided some open-source tools to read these assemblies from the binary blobs. I used this code as a reference to create our own Assembly Store Reader. Plus, I leveraged ReadOnlyMemory and Span to efficiently load chunks of binary into memory, slicing and dicing them according to the manifest and store headers. This way, we avoid reallocating byte arrays for reading, streaming, and decompressing data for the PEReader class. As always our Raygun4Net and related code is open source, and you can check out the specifics for this feature here.
To help visualize the process of locating, extracting, and decompressing the assemblies for .NET MAUI Android, I created a small workflow representation:
Here’s a small snippet of the LZ4 decompression code using ReadOnlyMemory and leveraging the fantastic work from the K4os.Compression.LZ4 nuget package. By passing around a ReadOnlyMemory<byte> we can easily read values directly from memory, and be assured that we aren’t re-allocating byte arrays unnecessarily.
private bool TryDecompressLZ4AssemblyBytes(ReadOnlyMemory<byte> encodedAssembly, out ReadOnlyMemory<byte> decompressedBytes)
{
var magicHeader = BinaryPrimitives.ReadUInt32LittleEndian(encodedAssembly.Span[..4]);
var decompressedLength = BinaryPrimitives.ReadInt32LittleEndian(encodedAssembly.Span[8..12]);
if (magicHeader != CompressedDataMagic)
{
decompressedBytes = ReadOnlyMemory<byte>.Empty;
return false;
}
// Skip the header and move to the compressed bytes
var assemblyBytes = encodedAssembly.Slice(12);
var decompressedArray = new byte[decompressedLength];
try
{
var actualDecompressedSize = LZ4Codec.Decode(assemblyBytes.Span, decompressedArray);
if (actualDecompressedSize != decompressedLength)
{
throw new Exception($"Could not decompress bytes. Lengths do not match, expected {decompressedLength} but got {actualDecompressedSize}");
}
decompressedBytes = new ReadOnlyMemory<byte>(decompressedArray);
return true;
}
catch
{
decompressedBytes = ReadOnlyMemory<byte>.Empty;
}
return false;
}
I thought I’d also include some examples of what the manifest file and the assembly blobs look like. I’m still a nerd at heart, and it’s been too long (or maybe not long enough?) since I last had to open a hex editor and start inspecting binary. This was able to help me understand the exact structure of the blobs we were dealing with, and to help shape the code responsible for reading the assemblies.
Manifest File:
Assembly Blob, with a LZ4 compressed assembly highlighted:
Wrap-up
With Portable PDB support and offline storage now in Raygun4Net, you’ll catch every error, even during connectivity hiccups, complete with full stack traces. Adding these features was a tough but rewarding journey, and we hope they make your error tracking and debugging a whole lot easier.
Not a Raygun customer yet? Try out the full Crash Reporting application free for 14 days! No credit card required.