@vtex/node-error-report

Error parsing, serialization, sanitizing and reporting

Usage no npm install needed!

<script type="module">
  import vtexNodeErrorReport from 'https://cdn.skypack.dev/@vtex/node-error-report';
</script>

README

node-error-report

Version

This package is used as base for error parsing logic on node-vtex-api and vtex-toolbelt.

Install

$ yarn global add @vtex/node-error-report

API

The ErrorReportBase is a wrapper for node errors that provides features like error serialization, sanitization and error parsing (in case of request errors, for example).

Instantiation

It has the following constructor arguments:

kind [required]

This is supposed to be an error code to make error identification on a logs backend easier.

originalError [required]

This is the originalError to be wrapped by the ErrorReportBase - it will try to parse meaningful information on the error and add it to the parsedInfo instance variable.

config [required]

Configures error serialization aspects:

  • Max string size on each field.
  • Max object depth.

message [optional]

ErrorReportBase uses originalError.message as default error message. This allows to override it.

details [optional]

This is a object with additional details on the error, can be used in cases like:

try {
  content = readFileSync(filename)
} catch(err) {
  new ErrorReportBase({
      ...,
      details: {
          filename
      }
  })
}

Note that this is useful because sometimes errors thrown by standard functions doesn't help us to pinpoint the issue.

Serialization

The serialization task is done by the toObject method on ErrorReportBase. It will:

  • Remove circular references.
  • Truncate strings bigger than config.maxStringLength.
  • Sanitize jwt tokens (the token identification logic right now is not that good).

Also, the toObject will have error metadata, the details provided by whom instantiated the error, the original error stack, message and parsed info.

Error parsing

For now just axios request errors are parsed - some key information on the request are stripped out to a object (code).

Also it's possible to create parseable errors by implementing the ParseableError interface, e.g.:

export class EventSourceError extends Error implements ParseableError {
  public event: any
  public eventSourceInfo: EventSourceInfo
  constructor(event: any, eventSourceInfo: EventSourceInfo) {
    super(`SSE error on endpoint ${eventSourceInfo.url}`)
    this.eventSourceInfo = eventSourceInfo
    this.event = { ...event }
  }

  public getDetailsObject() {
    return {
      event: this.event,
      eventSourceInfo: this.eventSourceInfo,
    }
  }
}

When instantianting a ErrorReportBase and providing an EventSourceError object as the originalError, the parseInfo on ErrorReportBase will be the content returned by getDetailsObject.

Parsed info type guards

Some typescript type guards implemented are also exported:

  • isRequestInfo: Checks if the parsedInfo instance variable is the result of a request parsing.
  • isInfraErrorData: Checks if the requestInfo.response.data is a VTEX IO infra error (these errors can be used to improve the error report).

Helpers

createErrorReportBaseArgs

This function abstracts away some instantiation logic, it allows the function user to optionally specify the kind (if not specified a generic kind will be used - GenericError or RequestError) and abstracts away the config creation. The configs will be provided by:

maxStringLength: ErrorReportBase.MAX_ERROR_STRING_LENGTH
maxSerializationDepth: ErrorReportBase.MAX_SERIALIZATION_DEPTH

The ErrorReportBase allow these default values to be changed, e.g.:

ErrorReportBase.MAX_ERROR_STRING_LENGTH = 2048

Usage example

The ErrorReportBase class is supposed to be extended with functions for reporting the error to a log/tracing backend, for example:

export class ErrorReport extends ErrorReportBase {
  public static create(args: ErrorReportCreateArgs) {
    return new ErrorReport(createErrorReportBaseArgs(args))
  }

  constructor(args: ErrorReportBaseConstructorArgs) {
    const { workspace, account } = SessionManager.getSingleton()

    const env: ErrorEnv = {
      account,
      workspace,
      toolbeltVersion: pkg.version,
      nodeVersion: process.version,
      platform: getPlatform(),
      command: process.argv.slice(2).join(' '),
    }

    super({
      ...args,
      details: {
        ...args.details,
        env,
      },
    })
  }

  public sendToTelemetry() {
    if (!this.isErrorReported()) {
      TelemetryCollector.getCollector().registerError(this)
      this.markErrorAsReported()
    }

    return this
  }
}

The ErrorReportBase class keeps track of the report state of the error, to avoid sending the same originalError to the backend twice - that's the role of the methods isErrorReported and markErrorAsReported. These functions add metadata to the originalError as well, so if the same originalError is passed to another ErrorReportBase instance, the report state will be maintained.

Metrics

ErrorReportBase exposes the following metrics that can be reported to a metrics/logs backend and accessed via errorReporObj.metadata.metrics:

instantiationTime

On its instantiation, the ErrorReportBase class clones and sanitizes some fields from the arguments provided, for example:

  • All info extracted from RequestErrors are sanitized and cloned.
  • The details object is sanitized and cloned. This is done because later uses of these objects (or sub-objects) could be affected if ErrorReportBase didn't cloned them and changed anything (for example, it could sanitize the Authorization header, then later uses of the same request config would end up with auth errors).

But this may be costly (who knows?). Because of this, ErrorReportBase exposes the instantiationTime metric.