Search code examples
c#serilogsentrybreadcrumbs

Serilog Sentry "global" breadcrumbs in .NET C# windows forms


I'm very new to Sentry and trying to figure out how to add some "global" breadcrumbs to my application (Excel-DNA based Excel add-in, written in C#). I'm using the Serilog Sentry library and here is the initialization code:

    Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Verbose()
        .WriteTo.File($"{logFile}_.log", 
        rollingInterval: RollingInterval.Day,
        fileSizeLimitBytes: 8000000)
        .WriteTo.ExcelDnaLogDisplay(displayOrder: DisplayOrder.NewestFirst)
        .WriteTo.Sentry(o =>
        {
            // Debug and higher are stored as breadcrumbs (default is Information)
            o.MinimumBreadcrumbLevel = LogEventLevel.Debug;
            // Warning and higher is sent as event (default is Error)
            o.MinimumEventLevel = LogEventLevel.Warning;
            // If DSN is not set, the SDK will look for an environment variable called SENTRY_DSN. If nothing is found, SDK is disabled.
            o.Dsn = "dsn_goes_here";
            o.AttachStacktrace = true;
            // send PII like the username of the user logged in to the device
            o.SendDefaultPii = true;
        })
        .CreateLogger();

Everything works fine and I'm able to see the log messages in Sentry. Now I want to add some "global" breadcrumbs that should be sent with every message. I can use SentrySdk.AddBreadcrumb in, let's say, a form initialization and all Log.Error in that form code will send the breadcrumbs. But I'm also using public static functions defined in various classes and this approach doesn't work.

Is there a way to define the breadcrumbs in Sentry/Serilog initialization or somewhere where they will be used by any Log.Error in any class?


Solution

  • Breadcrumbs track things that happened, such as "window opened" or "button clicked". They are useful to produce a timeline of activity leading up to an event (such as an exception). They aren't as useful for bits of information that apply throughout the app.

    For that, Sentry has the concept of contexts. A lot of context is already provided automatically by the SDK, such as the operating system type and version, the amount of memory available, etc.

    You can add your own custom context by adding it to the scope. Adapting the example in your answer:

    SentrySdk.ConfigureScope(scope =>
    {
        scope.Contexts["XLL path"] = ExcelDnaUtil.XllPath;
    });
    

    Add this right after you initialize the SDK, so that it is on the scope used whenever events are generated from your app. You should also set o.IsGlobalModeEnabled = true in your options - since this is a client app. That will ensure that the scope where you set the context is used globally throughout your app.

    Why not tags then (as your answer gave)? Mainly because tags are meant for items that are indexed and searchable. Each issue also gives a breakdown of tags that apply to that issue, by percentage. A good example of a tag would be "Feature XYZ" or "Version 1234". File paths are going to have too much variability to be useful as tags - so they're better off as context.

    Lastly, I'll add that using Sentry for add-ins or plugins can sometimes be problematic. The main reason is that the SDK is designed to be initialized once and only once, for the lifetime of the application. In a plugin environment, you may not always have a good place to perform static or singleton initialization. You might also not have control over other plugins that are installed. For example, if two different plugins initialized Sentry with separate DSNs, you could end up capturing each other's events. Much of this depends on the nature of the particular application hosting the plugin though. If sufficiently sandboxed, some of those limitations can be overcome. For example, some applications create a separate process for their plugins to run in. I'm not particularly familiar with Excel-DNA, so I can't speak to what your particular situation might be.