Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Rich JavaScript errors

License

NotificationsYou must be signed in to change notification settings

TritonDataCenter/node-verror

Repository files navigation

This module provides several classes in support of Joyent'sBest Practices forError Handling in Node.js.If you find any of the behavior here confusing or surprising, check out thatdocument first.

The error classes here support:

  • printf-style arguments for the message
  • chains of causes
  • properties to provide extra information about the error
  • creating your own subclasses that support all of these

The classes here are:

  • VError, for chaining errors while preserving each one's error message.This is useful in servers and command-line utilities when you want topropagate an error up a call stack, but allow various levels to add their owncontext. See examples below.
  • WError, for wrapping errors while hiding the lower-level messages from thetop-level error. This is useful for API endpoints where you don't want toexpose internal error messages, but you still want to preserve the error chainfor logging and debugging.
  • SError, which is just like VError but interprets printf-style argumentsmore strictly.
  • MultiError, which is just an Error that encapsulates one or more othererrors. (This is used for parallel operations that return several errors.)

Quick start

First, install the package:

npm install verror

If nothing else, you can use VError as a drop-in replacement for the built-inJavaScript Error class, with the addition of printf-style messages:

varerr=newVError('missing file: "%s"','/etc/passwd');console.log(err.message);

This prints:

missing file: "/etc/passwd"

You can also pass acause argument, which is any other Error object:

varfs=require('fs');varfilename='/nonexistent';fs.stat(filename,function(err1){varerr2=newVError(err1,'stat "%s"',filename);console.error(err2.message);});

This prints out:

stat "/nonexistent": ENOENT, stat '/nonexistent'

which resembles how Unix programs typically report errors:

$ sort /nonexistentsort: open failed: /nonexistent: No such file or directory

To match the Unixy feel, when you print out the error, just prepend theprogram's name to the VError'smessage. Or just callnode-cmdutil.fail(your_verror), whichdoes this for you.

You can get the next-level Error usingerr.cause():

console.error(err2.cause().message);

prints:

ENOENT, stat '/nonexistent'

Of course, you can chain these as many times as you want, and it works with anykind of Error:

varerr1=newError('No such file or directory');varerr2=newVError(err1,'failed to stat "%s"','/junk');varerr3=newVError(err2,'request failed');console.error(err3.message);

This prints:

request failed: failed to stat "/junk": No such file or directory

The idea is that each layer in the stack annotates the error with a descriptionof what it was doing. The end result is a message that explains what happenedat each level.

You can also decorate Error objects with additional information so that callerscan not only handle each kind of error differently, but also construct their ownerror messages (e.g., to localize them, format them, group them by type, and soon). See the example below.

Deeper dive

The two main goals for VError are:

  • Make it easy to construct clear, complete error messages intended forpeople. Clear error messages greatly improve both user experience anddebuggability, so we wanted to make it easy to build them. That's why theconstructor takes printf-style arguments.
  • Make it easy to construct objects with programmatically-accessiblemetadata (which we callinformational properties). Instead of just saying"connection refused while connecting to 192.168.1.2:80", you can addproperties like"ip": "192.168.1.2" and"tcpPort": 80. This can be usedfor feeding into monitoring systems, analyzing large numbers of Errors (asfrom a log file), or localizing error messages.

To really make this useful, it also needs to be easy to compose Errors:higher-level code should be able to augment the Errors reported by lower-levelcode to provide a more complete description of what happened. Instead of saying"connection refused", you can say "operation X failed: connection refused".That's why VError supportscauses.

In order for all this to work, programmers need to know that it's generally safeto wrap lower-level Errors with higher-level ones. If you have existing codethat handles Errors produced by a library, you should be able to wrap thoseErrors with a VError to add information without breaking the error handlingcode. There are two obvious ways that this could break such consumers:

  • The error's name might change. People typically usename to determine whatkind of Error they've got. To ensure compatibility, you can create VErrorswith custom names, but this approach isn't great because it prevents you fromrepresenting complex failures. For this reason, VError providesfindCauseByName, which essentially asks: does this Erroror any of itscauses have this specific type? If error handling code usesfindCauseByName, then subsystems can construct very specific causal chainsfor debuggability and still let people handle simple cases easily. There's anexample below.
  • The error's properties might change. People often hang additional propertiesoff of Error objects. If we wrap an existing Error in a new Error, thoseproperties would be lost unless we copied them. But there are a variety ofboth standard and non-standard Error properties that shouldnot be copied inthis way: most obviouslyname,message, andstack, but alsofileName,lineNumber, and a few others. Plus, it's useful for some Error subclassesto have their own private properties -- and there'd be no way to know whetherthese should be copied. For these reasons, VError first-classes theseinformation properties. You have to provide them in the constructor, you canonly fetch them with theinfo() function, and VError takes care of makingsure properties from causes wind up in theinfo() output.

Let's put this all together with an example from the node-fast RPC library.node-fast implements a simple RPC protocol for Node programs. There's a serverand client interface, and clients make RPC requests to servers. Let's say theserver fails with an UnauthorizedError with message "user 'bob' is notauthorized". The client wraps all server errors with a FastServerError. Theclient also wraps all request errors with a FastRequestError that includes thename of the RPC call being made. The result of this failed RPC might look likethis:

name: FastRequestErrormessage: "request failed: server error: user 'bob' is not authorized"rpcMsgid: <unique identifier for this request>rpcMethod: GetObjectcause:    name: FastServerError    message: "server error: user 'bob' is not authorized"    cause:        name: UnauthorizedError        message: "user 'bob' is not authorized"        rpcUser: "bob"

When the caller usesVError.info(), the information properties are collapsedso that it looks like this:

message: "request failed: server error: user 'bob' is not authorized"rpcMsgid: <unique identifier for this request>rpcMethod: GetObjectrpcUser: "bob"

Taking this apart:

  • The error's message is a complete description of the problem. The caller canreport this directly to its caller, which can potentially make its way back toan end user (if appropriate). It can also be logged.
  • The caller can tell that the request failed on the server, rather than as aresult of a client problem (e.g., failure to serialize the request), atransport problem (e.g., failure to connect to the server), or something else(e.g., a timeout). They do this usingfindCauseByName('FastServerError')rather than checking thename field directly.
  • If the caller logs this error, the logs can be analyzed to aggregateerrors by cause, by RPC method name, by user, or whatever. Or theerror can be correlated with other events for the same rpcMsgid.
  • It wasn't very hard for any part of the code to contribute to this Error.Each part of the stack has just a few lines to provide exactly what it knows,with very little boilerplate.

It's not expected that you'd use these complex forms all the time. Despitesupporting the complex case above, you can still just do:

new VError("my service isn't working");

for the simple cases.

Reference: VError, WError, SError

VError, WError, and SError are convenient drop-in replacements forError thatsupport printf-style arguments, first-class causes, informational properties,and other useful features.

Constructors

The VError constructor has several forms:

/* * This is the most general form.  You can specify any supported options * (including "cause" and "info") this way. */newVError(options,sprintf_args...)/* * This is a useful shorthand when the only option you need is "cause". */newVError(cause,sprintf_args...)/* * This is a useful shorthand when you don't need any options at all. */newVError(sprintf_args...)

All of these forms construct a new VError that behaves just like the built-inJavaScriptError class, with some additional methods described below.

In the first form,options is a plain object with any of the followingoptional properties:

Option nameTypeMeaning
namestringDescribes what kind of error this is. This is intended for programmatic use to distinguish between different kinds of errors. Note that in modern versions of Node.js, this name is ignored in thestack property value, but callers can still use thename property to get at it.
causeany Error objectIndicates that the new error was caused bycause. Seecause() below. If unspecified, the cause will benull.
strictbooleanIf true, thennull andundefined values insprintf_args are passed through tosprintf(). Otherwise, these are replaced with the strings'null', and 'undefined', respectively.
constructorOptfunctionIf specified, then the stack trace for this error ends at functionconstructorOpt. Functions called byconstructorOpt will not show up in the stack. This is useful when this class is subclassed.
infoobjectSpecifies arbitrary informational properties that are available through theVError.info(err) static class method. See that method for details.

The second form is equivalent to using the first form with the specifiedcauseas the error's cause. This form is distinguished from the first form becausethe first argument is an Error.

The third form is equivalent to using the first form with all default optionvalues. This form is distinguished from the other forms because the firstargument is not an object or an Error.

TheWError constructor is used exactly the same way as theVErrorconstructor. TheSError constructor is also used the same way as theVError constructor except that in all cases, thestrict property isoverriden to `true.

Public properties

VError,WError, andSError all provide the same public properties asJavaScript's built-in Error objects.

Property nameTypeMeaning
namestringProgrammatically-usable name of the error.
messagestringHuman-readable summary of the failure. Programmatically-accessible details are provided throughVError.info(err) class method.
stackstringHuman-readable stack trace where the Error was constructed.

For all of these classes, the printf-style arguments passed to the constructorare processed withsprintf() to form a message. ForWError, this becomesthe completemessage property. ForSError andVError, this message isprepended to the message of the cause, if any (with a suitable separator), andthe result becomes themessage property.

Thestack property is managed entirely by the underlying JavaScriptimplementation. It's generally implemented using a getter function becauseconstructing the human-readable stack trace is somewhat expensive.

Class methods

The following methods are defined on theVError class and as exportedfunctions on theverror module. They're defined this way rather than usingmethods on VError instances so that they can be used on Errors not created withVError.

VError.cause(err)

Thecause() function returns the next Error in the cause chain forerr, ornull if there is no next error. See thecause argument to the constructor.Errors can have arbitrarily long cause chains. You can walk thecause chainby invokingVError.cause(err) on each subsequent return value. Iferr isnot aVError, the cause isnull.

VError.info(err)

Returns an object with all of the extra error information that's been associatedwith this Error and all of its causes. These are the properties passed in usingtheinfo option to the constructor. Properties not specified in theconstructor for this Error are implicitly inherited from this error's cause.

These properties are intended to provide programmatically-accessible metadataabout the error. For an error that indicates a failure to resolve a DNS name,informational properties might include the DNS name to be resolved, or even thelist of resolvers used to resolve it. The values of these properties shouldgenerally be plain objects (i.e., consisting only of null, undefined, numbers,booleans, strings, and objects and arrays containing only other plain objects).

VError.fullStack(err)

Returns a string containing the full stack trace, with all nested errors recursivelyreported as'caused by:' + err.stack.

VError.findCauseByName(err, name)

ThefindCauseByName() function traverses the cause chain forerr, lookingfor an error whosename property matches the passed inname value. If nomatch is found,null is returned.

If all you want is to knowwhether there's a cause (and you don't care what itis), you can useVError.hasCauseWithName(err, name).

If a vanilla error or a non-VError error is passed in, then there is no causechain to traverse. In this scenario, the function will check thenameproperty of onlyerr.

VError.hasCauseWithName(err, name)

Returns true if and only ifVError.findCauseByName(err, name) would returna non-null value. This essentially determines whethererr has any cause inits cause chain that has namename.

VError.errorFromList(errors)

Given an array of Error objects (possibly empty), return a single errorrepresenting the whole collection of errors. If the list has:

  • 0 elements, returnsnull
  • 1 element, returns the sole error
  • more than 1 element, returns a MultiError referencing the whole list

This is useful for cases where an operation may produce any number of errors,and you ultimately want to implement the usualcallback(err) pattern. You canaccumulate the errors in an array and then invokecallback(VError.errorFromList(errors)) when the operation is complete.

VError.errorForEach(err, func)

Convenience function for iterating an error that may itself be a MultiError.

In all cases,err must be an Error. Iferr is a MultiError, thenfunc isinvoked asfunc(errorN) for each of the underlying errors of the MultiError.Iferr is any other kind of error,func is invoked once asfunc(err). Inall cases,func is invoked synchronously.

This is useful for cases where an operation may produce any number of warningsthat may be encapsulated with a MultiError -- but may not be.

This function does not iterate an error's cause chain.

Examples

The "Demo" section above covers several basic cases. Here's a more advancedcase:

varerr1=newVError('something bad happened');/* ... */varerr2=newVError({'name':'ConnectionError','cause':err1,'info':{'errno':'ECONNREFUSED','remote_ip':'127.0.0.1','port':215}},'failed to connect to "%s:%d"','127.0.0.1',215);console.log(err2.message);console.log(err2.name);console.log(VError.info(err2));console.log(err2.stack);

This outputs:

failed to connect to "127.0.0.1:215": something bad happenedConnectionError{ errno: 'ECONNREFUSED', remote_ip: '127.0.0.1', port: 215 }ConnectionError: failed to connect to "127.0.0.1:215": something bad happened    at Object.<anonymous> (/home/dap/node-verror/examples/info.js:5:12)    at Module._compile (module.js:456:26)    at Object.Module._extensions..js (module.js:474:10)    at Module.load (module.js:356:32)    at Function.Module._load (module.js:312:12)    at Function.Module.runMain (module.js:497:10)    at startup (node.js:119:16)    at node.js:935:3

Information properties are inherited up the cause chain, with values at the topof the chain overriding same-named values lower in the chain. To continue thatexample:

varerr3=newVError({'name':'RequestError','cause':err2,'info':{'errno':'EBADREQUEST'}},'request failed');console.log(err3.message);console.log(err3.name);console.log(VError.info(err3));console.log(err3.stack);

This outputs:

request failed: failed to connect to "127.0.0.1:215": something bad happenedRequestError{ errno: 'EBADREQUEST', remote_ip: '127.0.0.1', port: 215 }RequestError: request failed: failed to connect to "127.0.0.1:215": something bad happened    at Object.<anonymous> (/home/dap/node-verror/examples/info.js:20:12)    at Module._compile (module.js:456:26)    at Object.Module._extensions..js (module.js:474:10)    at Module.load (module.js:356:32)    at Function.Module._load (module.js:312:12)    at Function.Module.runMain (module.js:497:10)    at startup (node.js:119:16)    at node.js:935:3

You can also print the complete stack trace of combinedErrors by usingVError.fullStack(err).

varerr1=newVError('something bad happened');/* ... */varerr2=newVError(err1,'something really bad happened here');console.log(VError.fullStack(err2));

This outputs:

VError: something really bad happened here: something bad happened    at Object.<anonymous> (/home/dap/node-verror/examples/fullStack.js:5:12)    at Module._compile (module.js:409:26)    at Object.Module._extensions..js (module.js:416:10)    at Module.load (module.js:343:32)    at Function.Module._load (module.js:300:12)    at Function.Module.runMain (module.js:441:10)    at startup (node.js:139:18)    at node.js:968:3caused by: VError: something bad happened    at Object.<anonymous> (/home/dap/node-verror/examples/fullStack.js:3:12)    at Module._compile (module.js:409:26)    at Object.Module._extensions..js (module.js:416:10)    at Module.load (module.js:343:32)    at Function.Module._load (module.js:300:12)    at Function.Module.runMain (module.js:441:10)    at startup (node.js:139:18)    at node.js:968:3

VError.fullStack is also safe to use on regularErrors, so feel free to useit whenever you need to extract the stack trace from anError, regardless ifit's aVError or not.

Reference: MultiError

MultiError is an Error class that represents a group of Errors. This is usedwhen you logically need to provide a single Error, but you want to preserveinformation about multiple underlying Errors. A common case is when you executeseveral operations in parallel and some of them fail.

MultiErrors are constructed as:

newMultiError(error_list)

error_list is an array of at least oneError object.

The cause of the MultiError is the first error provided. None of the otherVError options are supported. Themessage for a MultiError consists themessage from the first error, prepended with a message indicating that therewere other errors.

For example:

err=newMultiError([newError('failed to resolve DNS name "abc.example.com"'),newError('failed to resolve DNS name "def.example.com"'),]);console.error(err.message);

outputs:

first of 2 errors: failed to resolve DNS name "abc.example.com"

See the convenience functionVError.errorFromList, which is sometimes simplerto use than this constructor.

Public methods

errors()

Returns an array of the errors used to construct this MultiError.

Contributing

See separatecontribution guidelines.


[8]ページ先頭

©2009-2025 Movatter.jp