SDK Fingerprinting

In supported SDKs, you can override the Sentry default grouping passing the fingerprint attribute an array of strings. For example, in JavaScript you can group by the method, path and the err.statusCode like this:

function makeRequest(method, path, options) {
    return fetch(method, path, options).catch(function(err) {
        Sentry.withScope(function(scope) {
          // group errors together based on their request and response
          scope.setFingerprint([method, path, err.statusCode]);
          Sentry.captureException(err);
        });
    });
}

Similarly, you can pass {{ default }} in the fingerprint to extend the built-in behavior:

function makeRequest(method, path, options) {
    return fetch(method, path, options).catch(function(err) {
        Sentry.withScope(function(scope) {
          // extend the fingerprint with the request path
          scope.setFingerprint(['{{ default }}', path]);
          Sentry.captureException(err);
        });
    });
}

The following extra values are available for fingerprints:

  • {{ default }}: adds the default fingerprint values
  • {{ transaction }}: groups by the event’s transaction
  • {{ function }}: groups by the event’s function name
  • {{ type }}: groups by the event’s exception type name
  • {{ module }}: groups by the event’s module name
  • {{ package }}: groups by the event’s package name

Use Cases

Group errors more granularly

Your application queries an RPC interface or external API service, so the stack trace is generally the same (even if the outgoing request is very different).

The following example will split up the default group Sentry would create (represented by {{ default }}) further, taking some attributes on the error object into account:

public class MyRpcException : Exception
{
    // The name of the RPC function that was called (e.g. "getAllBlogArticles")
    public string Function { get; set; }

    // For example a HTTP status code returned by the server.
    public HttpStatusCode Code { get; set; }
}

using (SentrySdk.Init(o =>
{
    o.BeforeSend = @event =>
    {
        if (@event.Exception is MyRpcException ex)
        {
            @event.SetFingerprint(
                new []
                {
                    "{{ default }}",
                    ex.Function,
                    ex.Code.ToString(),
                }
            );
        }

        return @event;
    };
}
type MyRPCError struct {
	message      string
	functionName string
	errorCode    int
}

func (e MyRPCError) Error() string {
	return "MyRPCError: " + e.message
}

func (e MyRPCError) ErrorCode() string {
	return strconv.Itoa(e.errorCode)
}

func (e MyRPCError) FunctionName() string {
	return e.functionName
}

sentry.Init(sentry.ClientOptions{
	// ...
	BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
		if ex, ok := hint.OriginalException.(MyRPCError); ok {
			event.Fingerprint = []string{"{{ default }}", ex.ErrorCode(), ex.FunctionName()}
		}

		return event
	},
})
class MyRPCError extends Error {
  constructor(message, functionName, errorCode) {
    super(message);

    // The name of the RPC function that was called (e.g. "getAllBlogArticles")
    this.functionName = functionName;

    // For example a HTTP status code returned by the server.
    this.errorCode = errorCode;
  }
}

Sentry.init({
  ...,
  beforeSend: function(event, hint) {
    const exception = hint.originalException;

    if (exception instanceof MyRPCError) {
      event.fingerprint = [
        '{{ default }}',
        String(exception.functionName),
        String(exception.errorCode)
      ];
    }

    return event;
  }
});
#include <string>
#include <sentry.h>

class MyRpcError {
   public:
    std::string function;
    std::string error_code;

    MyRpcError(std::string function, std::string error_code)
        : function(function), error_code(error_code) {
    }
};

int main() {
    /* some code that emits MyRpcError */
    MyRpcError e(/* ... */);
    
    sentry_value_t fingerprint = sentry_value_new_list();
    sentry_value_append(fingerprint, sentry_value_new_string("{{ default }}"));
    sentry_value_append(fingerprint, sentry_value_new_string(e.function.c_str()));
    sentry_value_append(fingerprint, sentry_value_new_string(e.error_code.c_str()));

    sentry_value_t event = sentry_value_new_event();
    sentry_value_set_by_key(event, "fingerprint", fingerprint);
    /* add more attributes... */
    sentry_capture_event(event);
}
class MyRPCError(Exception):
    # The name of the RPC function that was called (e.g. "getAllBlogArticles")
    function = None

    # For example a HTTP status code returned by the server.
    error_code = None

def before_send(event, hint):
    if 'exc_info' not in hint:
        return event

    exception = hint['exc_info'][1]
    if isinstance(exception, MyRPCError):
        event['fingerprint'] = [
            '{{ default }}',
            str(exception.function),
            str(exception.error_code)
        ]

    return event

sentry_sdk.init(..., before_send=before_send)

Group errors more aggressively

A generic error, such as a database connection error, has many different stack traces and never groups together.

The following example will just completely overwrite Sentry’s grouping by omitting {{ default }} from the array:

using (SentrySdk.Init(o =>
{
    o.BeforeSend = @event =>
    {
        if (@event.Exception is SqlConnection ex)
        {
            @event.SetFingerprint(new [] { "database-connection-error" });
        }

        return @event;
    };
}
type DatabaseConnectionError struct {
	message string
}

func (e DatabaseConnectionError) Error() string {
	return e.message
}

sentry.Init(sentry.ClientOptions{
	// ...
	BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
		if ex, ok := hint.OriginalException.(DatabaseConnectionError); ok {
			event.Fingerprint = []string{"database-connection-error"}
		}

		return event
	},
})
class DatabaseConnectionError extends Error {}

Sentry.init({
  ...,
  beforeSend: function(event, hint) {
    const exception = hint.originalException;

    if (exception instanceof DatabaseConnectionError) {
      event.fingerprint = ['database-connection-error'];
    }

    return event;
  }
});
#include <sentry.h>

sentry_value_t fingerprint = sentry_value_new_list();
sentry_value_append(
    fingerprint, sentry_value_new_string("database-connection-error"));

sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "fingerprint", fingerprint);
/* add more attributes... */
sentry_capture_event(event);
class DatabaseConnectionError(Exception):
    pass

def before_send(event, hint):
    if 'exc_info' not in hint:
        return event

    exception = hint['exc_info'][1]
    if isinstance(exception, DatabaseConnectionError):
        event['fingerprint'] = ['database-connection-error']

    return event

sentry_sdk.init(..., before_send=before_send)