Green-Software-Foundation / if

Impact Framework
https://if.greensoftware.foundation/
MIT License
155 stars 40 forks source link

Improve error messages emitted from IF and plugins #593

Closed jmcook1186 closed 4 months ago

jmcook1186 commented 7 months ago

Sub of #655

What

Define and implement standards for error reporting that enable plugin developers to report clear, actionable information to users and make use of our IF logging protocols.

Why

As a user I want to be able to diagnose problems with IF from the error messages. As a developer I want to work with the IF logger and emit error messages in a way that integrates well with IF.

Context

We want to emit error messages using a finite set of predefined error classes so that we can provide specific documentation and advice to users in our documentation about the behaviours that lead to errors of each class and common remedies.

In our logging upgrade (#600 ) we are likely to implement a simple rerouting of console.x methods from plugins to the IF logger so that they can be reformatted and responsive to IF CLI flags. We can also use this same flow to improve how IF handles errors emitted from plugins.

First, we encourage plugin builders to use console.error() for their error messages and console.warn for warnings. IF then routes these to the Winston logger and prepends the name of the plugin that raised the error. The logger can also do some validation, for example we want error messages to come in the form:

CLASS: message

where the class is one of the predefined classes we publish in an if-core package. That package should include our errors.ts and strings.ts files so that they can be imported by plugin developers and we can refine and add to the files by creating new releases developers can install, rather than trying to convince plugin developers to copy files manually.

All a plugin builder need do is import the error classes they need from if-core and use console.error. Our IF logger handles everything else.

So, the actual actions to satisfy this criterion are to:

Enhancing the error class list

Here's the list of error classes we currently use across our IF features and plugins

IF errors CliInputError ManifestValidationError ModuleInitializationError InvalidAggregationParamError InvalidGroupingError PluginCredentialError PluginInitalizationError WriteFileError AuthorizationError (plugin) APIRequestError (plugin) InputValidationError (plugin) UnsupportedValueError (plugin) ConfigValidationError (plugin) ReadFileError (plugin) WriteFileError (plugin) MakeDirectoryError (plugin)

Today we also maintain a set of messages in if/src/config/strings.ts. These are messages that can be emitted attached to one of the error classes. However, there are examples across the if repository and elsewhere where some messages are defines in strings.ts and others are written inline where the error is emitted, for example here are two errors emitted in time-sync.ts:

in this case the error is imported from strings

throw new InputValidationError(INVALID_OBSERVATION_OVERLAP);

in this case the error is defined inline

  throw new InputValidationError(
    `Unexpected date datatype: ${typeof date}: ${date}`
  );

It would be better to have all error messages from IF features and builtins defined in strings.ts and avoid defining them inline wherever possible, as this approach makes them more reusable across IF and plugins and enables us to document them centrally.

We should also consider deprecating the errorBuilder function everywhere across IF, as this is just a very simple function that we can do without but it makes it harder for plugins to conform to our error handling norms so we should remove it.

So the actions to satisfy this criterion are:

SoW

InputValidationError could be broken into more specific classes that describe what has actually failed, for example ParameterIsInvalid, RequiredParameterNotFound etc

We can have some exceptions that we can try to automatically recover from, e.g. RetryException (the plugin hit some rate limit and wants us to retry in X seconds).

Acceptance criteria

narekhovhannisyan commented 7 months ago

Currently, I have attached a branch with a possible solution to this problem.

  1. Separation of errors by logical chunks such as CLI, Manifest, Plugin, etc.
  2. Log, warn, error and info have appropriate colors.
  3. Formatting will look like this:
(severity)[scope]
<message here>
Screenshot 2024-04-12 at 21 19 29 Screenshot 2024-04-12 at 21 20 11
  1. Still haven't covered ordinary console class logs, will work on it.
  2. Exhaust can also be included in scopes.
jawache commented 6 months ago

Some initial thoughts on this.

jmcook1186 commented 6 months ago

@narekhovhannisyan @jawache let's spike on this because I anticipate this blocking some other priority tasks such as https://github.com/Green-Software-Foundation/if/issues/337 and https://github.com/Green-Software-Foundation/if/issues/615. We need to determine:

Some of this might be covered by @narekhovhannisyan 's work detailed in comments above, but we need to understand where we are and get aligned on what specific outcomes we want to see.

jawache commented 6 months ago

Thanks @jmcook1186, the top level issue for all of this is this in my opinion: https://github.com/Green-Software-Foundation/if/issues/600 that's what I put in the Notion doc.

The intention of that issue was a broader discussion, based partly on on my personal challenges over the last few months trying to build manifest files and how our logging is lacking on many levels (not just the way we express errors in the console).

I'll put my thoughts on the top level ticket but I'd rather we have a broader conversation first, and then from that narrow into specific tasks like how we want exceptions to be logged.

zanete commented 6 months ago

Blocked as waiting for #600

zanete commented 5 months ago

needs a sign off after the call on tuesday next week

zanete commented 5 months ago

@narekhovhannisyan please review the AC to confirm it's clear / ready for dev + add a size

zanete commented 5 months ago

@narekhovhannisyan please see the above comment to get this issue moving to the next stage

narekhovhannisyan commented 5 months ago

@jmcook1186

Add documentation to if.greensoftware.foundation and plugin template repo explaining that plugin builders should raise errors using console.error({CLASS}:{message}) and that the classes are to be found in errors.ts int he template repo (identical to the same file in the IF repo). 

Here it says that users should rise error via console.error. However, users should throw an error to raise instead of logging. So it means our logger should handle formatting instead of the end user which will simplify user experience.

narekhovhannisyan commented 5 months ago

moving strings.ts will create difficulties for us and for the users since we have to create PRs every time to add error message or fix already existing one. At the moment only common types and error classes can be moved. We will see on the go what else we can move.

narekhovhannisyan commented 5 months ago

Sizing as L

jmcook1186 commented 5 months ago

ok so if I understand correctly, users throw new Error() invoking one of our classes that they import from if-core. strings.ts can stay in IF and not migrate to if-core.

narekhovhannisyan commented 5 months ago

@jmcook1186 we can move global config validation as a utility to if-core. It's redundant and plugin developers should write the same structure again and again.

  const validateGlobalConfig = () => {
    if (!globalConfig) {
      throw new GlobalConfigError(MISSING_GLOBAL_CONFIG);
    }

    const globalConfigSchema = z.object({
      coefficient: z.number(),
      'input-parameter': z.string().min(1),
      'output-parameter': z.string().min(1),
    });

    return validate<z.infer<typeof globalConfigSchema>>(
      globalConfigSchema,
      globalConfig
    );
  };

We can convert it into function and make it utility.

narekhovhannisyan commented 5 months ago

@jmcook1186 If plugins return an error that we don’t recognize or can’t parse, we should fail loudly in the console and exit.

What kind of logging do we want for this? currently we have logging which forwards user to our issue template.

jmcook1186 commented 5 months ago

This should just refer to users using an error class that we don't already support - in which case we need to include an error class in if-core that covers unknown error classes!

e.g. error UnsupportedErrorClass: plugin threw error class: <errorClass> that is not recognised by Impact Framework

jmcook1186 commented 5 months ago

Maybe we need a generic error class to act as a catch-all that we apply whenever a plugin just uses raw console.log(), e.g. it gets parsed as Unspecified Error: The user has not provided an IF error class. The message was: <error message>

But if a user does provide an error class but it's not one of our supported ones, it throws an UnsupportedErrorClass exception.

wyt?

zanete commented 5 months ago

PR of First part will be ready this afternoon There will be at leat 4 PRs in total This feels like going over L estimate

narekhovhannisyan commented 5 months ago

@jmcook1186 I have another suggestion which is simplier in this case. At the moment we already have a handler for errors, and it already checks if error classes that are included in our list. If the error class is unknown, then logs it with the reference to issue. We can tune that behavior to error UnsupportedErrorClass: plugin threw error class: <errorClass> that is not recognized by Impact Framework. WDYT?

jmcook1186 commented 5 months ago

@narekhovhannisyan ok that seems good. What happens if the plugin builder provides no error class and just uses raw console.x?

narekhovhannisyan commented 5 months ago

@jmcook1186 we should ignore it in our error handling layer. It will be caught by logging handler

jmcook1186 commented 5 months ago

@narekhovhannisyan ok, so it would only be printed to the console if --debug is raised?

narekhovhannisyan commented 5 months ago

let me check

narekhovhannisyan commented 5 months ago

with debug flag it logs like this INFO: 2024-06-11T09:35:37.530Z: sum: test log without debug flag test log

jmcook1186 commented 5 months ago

ok that seems fine. I guess what we're saying to plugin builders is that if you want to give your users the best UX, tight integration to IF and access to docs and debugging support then you have to use our error handlers, and we'll make that as easy as possible, but if you don't we won't break your ability to emit messages to the console. That makes sense to me.

@narekhovhannisyan

narekhovhannisyan commented 5 months ago

@jmcook1186 agree. So we are limiting the use of error classes that are not supported from our side. Yeah?

zanete commented 4 months ago

@narekhovhannisyan to prepare a break down of the topics and how it could be divided between all who can help

narekhovhannisyan commented 4 months ago

@jmcook1186 can you please help me with this one?

the README for each builtin includes a comprehensive list of the error messages the builtin can emit, with reasons and remedies linking to the appropriate docs.
zanete commented 4 months ago

Related issue #602 for @jmcook1186