jhomlala / catcher

Flutter error catching & handling plugin. Handles and reports exceptions in your app!
Apache License 2.0
787 stars 177 forks source link

[FEATURE] Custom report modes based on stack trace #177

Closed jhass closed 2 years ago

jhass commented 3 years ago

Is your feature request related to a problem? Please describe.

I'm using a list of safe to ignore error messages to set the silent report mode and not bother users and myself with reports I can't do anything about because they're for example framework bugs.

Not only but especially since upgrading to Dart's null safety just the error message often doesn't contain enough information to make a good judgement on this.

In example I'm seeing a number of crashes now where the error message is just:

Null check operator used on a null value

Ignoring some instances of these is fine for me because they are for example non-fatal and in code I don't control. However I wouldn't want to ignore all instances of these since there could be instances which are in fact bugs in my code.

Describe the solution you'd like

The missing piece of information is often found in the stack trace. For example the function or package name raising the error could be a good heuristic to ignore certain generic errors.

Therefore it would be great to specify custom report modes and error handlers not only based on the exception message but also the stack trace.

I'm not sure how this should look like API wise, I can imagine three scenarios:

  1. The existing matching logic is extended to match the map key not only against the message but a combination of the message and the stringified stack trace. Potentially a breaking change since now there could be matches for some users where there previously weren't
  2. A second custom handler map is introduced that implements the same matching logic currently available for the message against the stringified stack trace. This is entirely backwards compatible but it could become challenging to find non-confusing naming for these.
  3. The matching logic could be put into the responsibility of the library users. That is is instead of a custom handler map there could be a callback that should return a custom handler or null to use the default one. The callback would receive both, the exception object and the stack trace. A backwards compatible implementation of this could implement the current matching logic on top of this new callback and forbid giving both, the map and the callback.
jhomlala commented 3 years ago

@jhass I think it can be solved without adding new feature to Catcher. You can use filterFunction to filter errors which you don't want to handle. These errors will be skipped and not reported anywhere. You can use 'filterFunction' with explicitExceptionHandlersMap to solve your problem.

Here's an example:

import 'package:catcher/catcher.dart';
import 'package:flutter/material.dart';

void main() {
  CatcherOptions debugOptions = CatcherOptions(DialogReportMode(), [
    ConsoleHandler(),
  ], filterFunction: (Report report) {
    var stackTraceText = report.stackTrace.toString();
    if (stackTraceText.contains("ChildWidget.generateFirstError")) {
      ///We don't want this error to be handled
      print("Skipped error from first button");
      return false;
    }
    return true;
  }, explicitExceptionHandlersMap: {
    "Second": ToastHandler(),
  },);

  Catcher(
      runAppFunction: () {
        runApp(MyApp());
      },
      debugConfig: debugOptions);
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: Catcher.navigatorKey,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: ChildWidget(),
      ),
    );
  }
}

class ChildWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          TextButton(
            child: Text("Generate first error"),
            onPressed: () => generateFirstError(),
          ),
          TextButton(
            child: Text("Generate second error"),
            onPressed: () => generateSecondError(),
          ),
        ],
      ),
    );
  }

  void generateFirstError() async {
    throw ArgumentError("First");
  }

  void generateSecondError() async {
    throw ArgumentError("Second");
  }
}
jhass commented 3 years ago

Yeah I think filterFunc was unreleased when I opened this, meanwhile I'm using it for this purpose. I still feel it's the less elegant solution to my proposal number 3. to be honest, it has a lot less flexibility compared to what I propose here and my proposal is even generic enough to implement the filter functionality by just allowing to return null for skipping the handling.

jhomlala commented 3 years ago

@jhass I agree that your idea is great but right now I don't have plans for implementing it in Catcher. There's currently filter function and explicit report map which will solve your problems.

I'll leave this issue open for the future - maybe during some rework of Catcher I'll provide one filter function to solve all explicit error handling.