Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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
Appearance settings

A logger for just about everything.

License

NotificationsYou must be signed in to change notification settings

winstonjs/winston

A logger for just about everything.

Version npmnpm Downloadsbuild statuscoverage status

NPM

winston@3

See theUpgrade Guide for more information. Bug reports andPRs welcome!

Looking forwinston@2.x documentation?

Please note that the documentation below is forwinston@3.Read thewinston@2.x documentation.

Motivation

winston is designed to be a simple and universal logging library withsupport for multiple transports. A transport is essentially a storage devicefor your logs. Eachwinston logger can have multiple transports (see:Transports) configured at different levels (see:Logging levels). Forexample, one may want error logs to be stored in a persistent remote location(like a database), but all logs output to the console or a local file.

winston aims to decouple parts of the logging process to make it moreflexible and extensible. Attention is given to supporting flexibility in logformatting (see:Formats) & levels (see:Using custom logging levels), andensuring those APIs decoupled from the implementation of transport logging(i.e. how the logs are stored / indexed, see:Adding Custom Transports) tothe API that they exposed to the programmer.

Quick Start

TL;DR? Check out thequick start example in./examples/.There are a number of other examples in./examples/*.js.Don't see an example you think should be there? Submit a pull requestto add it!

Usage

The recommended way to usewinston is to create your own logger. Thesimplest way to do this is usingwinston.createLogger:

constwinston=require('winston');constlogger=winston.createLogger({level:'info',format:winston.format.json(),defaultMeta:{service:'user-service'},transports:[//// - Write all logs with importance level of `error` or higher to `error.log`//   (i.e., error, fatal, but not other levels)//newwinston.transports.File({filename:'error.log',level:'error'}),//// - Write all logs with importance level of `info` or higher to `combined.log`//   (i.e., fatal, error, warn, and info, but not trace)//newwinston.transports.File({filename:'combined.log'}),],});//// If we're not in production then log to the `console` with the format:// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `//if(process.env.NODE_ENV!=='production'){logger.add(newwinston.transports.Console({format:winston.format.simple(),}));}

You may also log directly via the default logger exposed byrequire('winston'), but this merely intended to be a convenient sharedlogger to use throughout your application if you so choose.Note that the default logger doesn't have any transports by default.You need add transports by yourself, and leaving the default logger without anytransports may produce a high memory usage issue.

Table of contents

Logging

Logging levels inwinston conform to the severity ordering specified byRFC5424:severity of all levels is assumed to be numericallyascendingfrom most important to least important.

constlevels={error:0,warn:1,info:2,http:3,verbose:4,debug:5,silly:6};

Creating your own Logger

You get started by creating a logger usingwinston.createLogger:

constlogger=winston.createLogger({transports:[newwinston.transports.Console(),newwinston.transports.File({filename:'combined.log'})]});

A logger accepts the following parameters:

NameDefaultDescription
level'info'Log only ifinfo.level is less than or equal to this level
levelswinston.config.npm.levelsLevels (and colors) representing log priorities
formatwinston.format.jsonFormatting forinfo messages (see:Formats)
transports[](No transports)Set of logging targets forinfo messages
exitOnErrortrueIf false, handled exceptions will not causeprocess.exit
silentfalseIf true, all logs are suppressed

The levels provided tocreateLogger will be defined as convenience methodson thelogger returned.

//// Logging//logger.log({level:'info',message:'Hello distributed log files!'});logger.info('Hello again distributed logs');

You can add or remove transports from thelogger once it has been providedto you fromwinston.createLogger:

constfiles=newwinston.transports.File({filename:'combined.log'});constconsole=newwinston.transports.Console();logger.clear()// Remove all transports.add(console)// Add console transport.add(files)// Add file transport.remove(console);// Remove console transport

You can also wholesale reconfigure awinston.Logger instance using theconfigure method:

constlogger=winston.createLogger({level:'info',transports:[newwinston.transports.Console(),newwinston.transports.File({filename:'combined.log'})]});//// Replaces the previous transports with those in the// new configuration wholesale.//constDailyRotateFile=require('winston-daily-rotate-file');logger.configure({level:'verbose',transports:[newDailyRotateFile(opts)]});

Creating child loggers

You can create child loggers from existing loggers to pass metadata overrides:

constlogger=winston.createLogger({transports:[newwinston.transports.Console(),]});constchildLogger=logger.child({requestId:'451'});

.child is likely to be bugged if you're also extending theLogger class, due to some implementation details that makethis keyword to point to unexpected things. Use with caution.

Streams,objectMode, andinfo objects

Inwinston, bothLogger andTransport instances are treated asobjectModestreams that accept aninfo object.

Theinfo parameter provided to a given format represents a single logmessage. The object itself is mutable. Everyinfo must have at least thelevel andmessage properties:

constinfo={level:'info',// Level of the logging messagemessage:'Hey! Log something?'// Descriptive message being logged.};

Propertiesbesides level and message are considered as "meta". i.e.:

const{ level, message, ...meta}=info;

Several of the formats inlogform itself add additional properties:

PropertyFormat added byDescription
splatsplat()String interpolation splat for%d %s-style messages.
timestamptimestamp()timestamp the message was received.
labellabel()Custom label associated with each message.
msms()Number of milliseconds since the previous log message.

As a consumer you may add whatever properties you wish –internal state ismaintained bySymbol properties:

  • Symbol.for('level')(READ-ONLY): equal tolevel property.Is treated as immutable by all code.
  • Symbol.for('message'): complete string message set by "finalizing formats":
    • json
    • logstash
    • printf
    • prettyPrint
    • simple
  • Symbol.for('splat'): additional string interpolation arguments.Usedexclusively bysplat() format.

These Symbols are stored in another package:triple-beam so that allconsumers oflogform can have the same Symbol reference. i.e.:

const{LEVEL,MESSAGE,SPLAT}=require('triple-beam');console.log(LEVEL===Symbol.for('level'));// trueconsole.log(MESSAGE===Symbol.for('message'));// trueconsole.log(SPLAT===Symbol.for('splat'));// true

NOTE: any{ message } property in ameta object provided willautomatically be concatenated to anymsg already provided: Forexample the below will concatenate 'world' onto 'hello':

logger.log('error','hello',{message:'world'});logger.info('hello',{message:'world'});

Formats

Formats inwinston can be accessed fromwinston.format. They areimplemented inlogform, a separatemodule fromwinston. This allows flexibility when writing your own transportsin case you wish to include a default format with your transport.

In modern versions ofnode template strings are very performant and are therecommended way for doing most end-user formatting. If you want to bespokeformat your logs,winston.format.printf is for you:

const{ createLogger, format, transports}=require('winston');const{ combine, timestamp, label, printf}=format;constmyFormat=printf(({ level, message, label, timestamp})=>{return`${timestamp} [${label}]${level}:${message}`;});constlogger=createLogger({format:combine(label({label:'right meow!'}),timestamp(),myFormat),transports:[newtransports.Console()]});

To see what built-in formats are available and learn more about creating yourown custom logging formats, seelogform.

Combining formats

Any number of formats may be combined into a single format usingformat.combine. Sinceformat.combine takes noopts, as a convenience itreturns pre-created instance of the combined format.

const{ createLogger, format, transports}=require('winston');const{ combine, timestamp, label, prettyPrint}=format;constlogger=createLogger({format:combine(label({label:'right meow!'}),timestamp(),prettyPrint()),transports:[newtransports.Console()]})logger.log({level:'info',message:'What time is the testing at?'});// Outputs:// { level: 'info',//   message: 'What time is the testing at?',//   label: 'right meow!',//   timestamp: '2017-09-30T03:57:26.875Z'}

String interpolation

Thelog method provides the string interpolation usingutil.format.Itmust be enabled usingformat.splat().

Below is an example that defines a format with string interpolation ofmessages usingformat.splat and then serializes the entireinfo messageusingformat.simple.

const{ createLogger, format, transports}=require('winston');constlogger=createLogger({format:format.combine(format.splat(),format.simple()),transports:[newtransports.Console()]});// info: test message my string {}logger.log('info','test message %s','my string');// info: test message 123 {}logger.log('info','test message %d',123);// info: test message first second {number: 123}logger.log('info','test message %s, %s','first','second',{number:123});

Filteringinfo Objects

If you wish to filter out a giveninfo Object completely when logging thensimply return a falsey value.

const{ createLogger, format, transports}=require('winston');// Ignore log messages if they have { private: true }constignorePrivate=format((info,opts)=>{if(info.private){returnfalse;}returninfo;});constlogger=createLogger({format:format.combine(ignorePrivate(),format.json()),transports:[newtransports.Console()]});// Outputs: {"level":"error","message":"Public error to share"}logger.log({level:'error',message:'Public error to share'});// Messages with { private: true } will not be written when logged.logger.log({private:true,level:'error',message:'This is super secret - hide it.'});

Use offormat.combine will respect any falsey values return and stopevaluation of later formats in the series. For example:

const{ format}=require('winston');const{ combine, timestamp, label}=format;constwillNeverThrow=format.combine(format(info=>{returnfalse})(),// Ignores everythingformat(info=>{thrownewError('Never reached')})());

Creating custom formats

Formats are prototypal objects (i.e. class instances) that define a singlemethod:transform(info, opts) and return the mutatedinfo:

  • info: an object representing the log message.
  • opts: setting specific to the current instance of the format.

They are expected to return one of two things:

  • Aninfo Object representing the modifiedinfo argument. Objectreferences need not be preserved if immutability is preferred. All currentbuilt-in formats considerinfo mutable, but [immutablejs] is beingconsidered for future releases.
  • A falsey value indicating that theinfo argument should be ignored by thecaller. (See:Filteringinfo Objects) below.

winston.format is designed to be as simple as possible. To define a newformat, simply pass it atransform(info, opts) function to get a newFormat.

The namedFormat returned can be used to create as many copies of the givenFormat as desired:

const{ format}=require('winston');constvolume=format((info,opts)=>{if(opts.yell){info.message=info.message.toUpperCase();}elseif(opts.whisper){info.message=info.message.toLowerCase();}returninfo;});// `volume` is now a function that returns instances of the format.constscream=volume({yell:true});console.dir(scream.transform({level:'info',message:`sorry for making you YELL in your head!`},scream.options));// {//   level: 'info'//   message: 'SORRY FOR MAKING YOU YELL IN YOUR HEAD!'// }// `volume` can be used multiple times to create different formats.constwhisper=volume({whisper:true});console.dir(whisper.transform({level:'info',message:`WHY ARE THEY MAKING US YELL SO MUCH!`},whisper.options));// {//   level: 'info'//   message: 'why are they making us yell so much!'// }

Logging Levels

Logging levels inwinston conform to the severity ordering specified byRFC5424:severity of all levels is assumed to be numericallyascendingfrom most important to least important.

Eachlevel is given a specific integer priority. The higher the priority themore important the message is considered to be, and the lower thecorresponding integer priority. For example, as specified exactly in RFC5424thesyslog levels are prioritized from 0 to 7 (highest to lowest).

{emerg:0,alert:1,crit:2,error:3,warning:4,notice:5,info:6,debug:7}

Similarly,npm logging levels are prioritized from 0 to 6 (highest tolowest):

{error:0,warn:1,info:2,http:3,verbose:4,debug:5,silly:6}

If you do not explicitly define the levels thatwinston should use, thenpm levels above will be used.

Using Logging Levels

Setting the level for your logging message can be accomplished in one of twoways. You can pass a string representing the logging level to the log() methodor use the level specified methods defined on every winston Logger.

//// Any logger instance//logger.log('silly',"127.0.0.1 - there's no place like home");logger.log('debug',"127.0.0.1 - there's no place like home");logger.log('verbose',"127.0.0.1 - there's no place like home");logger.log('info',"127.0.0.1 - there's no place like home");logger.log('warn',"127.0.0.1 - there's no place like home");logger.log('error',"127.0.0.1 - there's no place like home");logger.info("127.0.0.1 - there's no place like home");logger.warn("127.0.0.1 - there's no place like home");logger.error("127.0.0.1 - there's no place like home");//// Default logger//winston.log('info',"127.0.0.1 - there's no place like home");winston.info("127.0.0.1 - there's no place like home");

winston allows you to define alevel property on each transport whichspecifies themaximum level of messages that a transport should log. Forexample, using thesyslog levels you could log onlyerror messages to theconsole and everythinginfo and below to a file (which includeserrormessages):

constlogger=winston.createLogger({levels:winston.config.syslog.levels,transports:[newwinston.transports.Console({level:'error'}),newwinston.transports.File({filename:'combined.log',level:'info'})]});

You may also dynamically change the log level of a transport:

consttransports={console:newwinston.transports.Console({level:'warn'}),file:newwinston.transports.File({filename:'combined.log',level:'error'})};constlogger=winston.createLogger({transports:[transports.console,transports.file]});logger.info('Will not be logged in either transport!');transports.console.level='info';transports.file.level='info';logger.info('Will be logged in both transports!');

winston supports customizable logging levels, defaulting to npm stylelogging levels. Levels must be specified at the time of creating your logger.

Using Custom Logging Levels

In addition to the predefinednpm,syslog, andcli levels available inwinston, you can also choose to define your own:

constmyCustomLevels={levels:{foo:0,bar:1,baz:2,foobar:3},colors:{foo:'blue',bar:'green',baz:'yellow',foobar:'red'}};constcustomLevelLogger=winston.createLogger({levels:myCustomLevels.levels});customLevelLogger.foobar('some foobar level-ed message');

Although there is slight repetition in this data structure, it enables simpleencapsulation if you do not want to have colors. If you do wish to havecolors, in addition to passing the levels to the Logger itself, you must makewinston aware of them:

winston.addColors(myCustomLevels.colors);

This enables loggers using thecolorize formatter to appropriately color and stylethe output of custom levels.

Additionally, you can also change background color and font style.For example,

baz:'italic yellow',foobar:'bold red cyanBG'

Possible options are below.

  • Font styles:bold,dim,italic,underline,inverse,hidden,strikethrough.

  • Font foreground colors:black,red,green,yellow,blue,magenta,cyan,white,gray,grey.

  • Background colors:blackBG,redBG,greenBG,yellowBG,blueBGmagentaBG,cyanBG,whiteBG

Colorizing Standard logging levels

To colorize the standard logging level add

winston.format.combine(winston.format.colorize(),winston.format.simple());

wherewinston.format.simple() is whatever other formatter you want to use. Thecolorize formatter must come before any formatters adding text you wish to color.

Colorizing full log line when json formatting logs

To colorize the full log line with the json formatter you can apply the following

winston.format.combine(winston.format.json(),winston.format.colorize({all:true}));

Transports

There are severalcore transports included inwinston, which leverage thebuilt-in networking and file I/O offered by Node.js core. In addition, thereareadditional transports written by members of the community.

Multiple transports of the same type

It is possible to use multiple transports of the same type e.g.winston.transports.File when you construct the transport.

constlogger=winston.createLogger({transports:[newwinston.transports.File({filename:'combined.log',level:'info'}),newwinston.transports.File({filename:'errors.log',level:'error'})]});

If you later want to remove one of these transports you can do so by using thetransport itself. e.g.:

constcombinedLogs=logger.transports.find(transport=>{returntransport.filename==='combined.log'});logger.remove(combinedLogs);

Adding Custom Transports

Adding a custom transport is easy. All you need to do is accept any optionsyou need, implement a log() method, and consume it withwinston.

constTransport=require('winston-transport');constutil=require('util');//// Inherit from `winston-transport` so you can take advantage// of the base functionality and `.exceptions.handle()`.//module.exports=classYourCustomTransportextendsTransport{constructor(opts){super(opts);//// Consume any custom options here. e.g.:// - Connection information for databases// - Authentication information for APIs (e.g. loggly, papertrail,//   logentries, etc.).//}log(info,callback){setImmediate(()=>{this.emit('logged',info);});// Perform the writing to the remote servicecallback();}};

Common Transport options

As every transport inherits fromwinston-transport, it's possible to seta custom format and a custom log level on each transport separately:

constlogger=winston.createLogger({transports:[newwinston.transports.File({filename:'error.log',level:'error',format:winston.format.json()}),newwinston.transports.Http({level:'warn',format:winston.format.json()}),newwinston.transports.Console({level:'info',format:winston.format.combine(winston.format.colorize(),winston.format.simple())})]});

Exceptions

Handling Uncaught Exceptions with winston

Withwinston, it is possible to catch and loguncaughtException eventsfrom your process. With your own logger instance you can enable this behaviorwhen it's created or later on in your applications lifecycle:

const{ createLogger, transports}=require('winston');// Enable exception handling when you create your logger.constlogger=createLogger({transports:[newtransports.File({filename:'combined.log'})],exceptionHandlers:[newtransports.File({filename:'exceptions.log'})]});// Or enable it later on by adding a transport or using `.exceptions.handle`constlogger=createLogger({transports:[newtransports.File({filename:'combined.log'})]});// Call exceptions.handle with a transport to handle exceptionslogger.exceptions.handle(newtransports.File({filename:'exceptions.log'}));

If you want to use this feature with the default logger, simply call.exceptions.handle() with a transport instance.

//// You can add a separate exception logger by passing it to `.exceptions.handle`//winston.exceptions.handle(newwinston.transports.File({filename:'path/to/exceptions.log'}));//// Alternatively you can set `handleExceptions` to true when adding transports// to winston.//winston.add(newwinston.transports.File({filename:'path/to/combined.log',handleExceptions:true}));

To Exit or Not to Exit

By default, winston will exit after logging an uncaughtException. If this isnot the behavior you want, setexitOnError = false

constlogger=winston.createLogger({exitOnError:false});//// or, like this://logger.exitOnError=false;

When working with custom logger instances, you can pass in separate transportsto theexceptionHandlers property or sethandleExceptions on anytransport.

Example 1
constlogger=winston.createLogger({transports:[newwinston.transports.File({filename:'path/to/combined.log'})],exceptionHandlers:[newwinston.transports.File({filename:'path/to/exceptions.log'})]});
Example 2
constlogger=winston.createLogger({transports:[newwinston.transports.Console({handleExceptions:true})],exitOnError:false});

TheexitOnError option can also be a function to prevent exit on onlycertain types of errors:

functionignoreEpipe(err){returnerr.code!=='EPIPE';}constlogger=winston.createLogger({exitOnError:ignoreEpipe});//// or, like this://logger.exitOnError=ignoreEpipe;

Rejections

Handling Uncaught Promise Rejections with winston

Withwinston, it is possible to catch and logunhandledRejection eventsfrom your process. With your own logger instance you can enable this behaviorwhen it's created or later on in your applications lifecycle:

const{ createLogger, transports}=require('winston');// Enable rejection handling when you create your logger.constlogger=createLogger({transports:[newtransports.File({filename:'combined.log'})],rejectionHandlers:[newtransports.File({filename:'rejections.log'})]});// Or enable it later on by adding a transport or using `.rejections.handle`constlogger=createLogger({transports:[newtransports.File({filename:'combined.log'})]});// Call rejections.handle with a transport to handle rejectionslogger.rejections.handle(newtransports.File({filename:'rejections.log'}));

If you want to use this feature with the default logger, simply call.rejections.handle() with a transport instance.

//// You can add a separate rejection logger by passing it to `.rejections.handle`//winston.rejections.handle(newwinston.transports.File({filename:'path/to/rejections.log'}));//// Alternatively you can set `handleRejections` to true when adding transports// to winston.//winston.add(newwinston.transports.File({filename:'path/to/combined.log',handleRejections:true}));

Profiling

In addition to logging messages and metadata,winston also has a simpleprofiling mechanism implemented for any logger:

//// Start profile of 'test'//logger.profile('test');setTimeout(function(){//// Stop profile of 'test'. Logging will now take place://   '17 Jan 21:00:00 - info: test duration=1000ms'//logger.profile('test');},1000);

Also you can start a timer and keep a reference that you can call.done()on:

// Returns an object corresponding to a specific timing. When done// is called the timer will finish and log the duration. e.g.://constprofiler=logger.startTimer();setTimeout(function(){profiler.done({message:'Logging message'});},1000);

All profile messages are set to 'info' level by default, and both message andmetadata are optional. For individual profile messages, you can override the default log level by supplying a metadata object with alevel property:

logger.profile('test',{level:'debug'});

Querying Logs

winston supports querying of logs with Loggly-like options.See LogglySearch API. Specifically:File,Couchdb,Redis,Loggly,Nssocket, andHttp.

constoptions={from:newDate()-(24*60*60*1000),until:newDate(),limit:10,start:0,order:'desc',fields:['message']};//// Find items logged between today and yesterday.//logger.query(options,function(err,results){if(err){/* TODO: handle me */throwerr;}console.log(results);});

Streaming Logs

Streaming allows you to stream your logs back from your chosen transport.

//// Start at the end.//winston.stream({start:-1}).on('log',function(log){console.log(log);});

Further Reading

Using the Default Logger

The default logger is accessible through thewinston module directly. Anymethod that you could call on an instance of a logger is available on thedefault logger:

constwinston=require('winston');winston.log('info','Hello distributed log files!');winston.info('Hello again distributed logs');winston.level='debug';winston.log('debug','Now my debug messages are written to console!');

By default, no transports are set on the default logger. You mustadd or remove transports via theadd() andremove() methods:

constfiles=newwinston.transports.File({filename:'combined.log'});constconsole=newwinston.transports.Console();winston.add(console);winston.add(files);winston.remove(console);

Or do it with one call to configure():

winston.configure({transports:[newwinston.transports.File({filename:'somefile.log'})]});

For more documentation about working with each individual transport supportedbywinston see thewinston Transports document.

Awaiting logs to be written inwinston

Often it is useful to wait for your logs to be written before exiting theprocess. Each instance ofwinston.Logger is also a [Node.js stream]. Afinish event will be raised when all logs have flushed to all transportsafter the stream has been ended.

consttransport=newwinston.transports.Console();constlogger=winston.createLogger({transports:[transport]});logger.on('finish',function(info){// All `info` log messages has now been logged});logger.info('CHILL WINSTON!',{seriously:true});logger.end();

It is also worth mentioning that the logger also emits an 'error' eventif an error occurs within the logger itself whichyou should handle or suppress if you don't want unhandled exceptions:

//// Handle errors originating in the logger itself//logger.on('error',function(err){/* Do Something */});

Working with multiple Loggers in winston

Often in larger, more complex, applications it is necessary to have multiplelogger instances with different settings. Each logger is responsible for adifferent feature area (or category). This is exposed inwinston in twoways: throughwinston.loggers and instances ofwinston.Container. In fact,winston.loggers is just a predefined instance ofwinston.Container:

constwinston=require('winston');const{ format}=winston;const{ combine, label, json}=format;//// Configure the logger for `category1`//winston.loggers.add('category1',{format:combine(label({label:'category one'}),json()),transports:[newwinston.transports.Console({level:'silly'}),newwinston.transports.File({filename:'somefile.log'})]});//// Configure the logger for `category2`//winston.loggers.add('category2',{format:combine(label({label:'category two'}),json()),transports:[newwinston.transports.Http({host:'localhost',port:8080})]});

Now that your loggers are setup, you can require winstonin any file in yourapplication and access these pre-configured loggers:

constwinston=require('winston');//// Grab your preconfigured loggers//constcategory1=winston.loggers.get('category1');constcategory2=winston.loggers.get('category2');category1.info('logging to file and console transports');category2.info('logging to http transport');

If you prefer to manage theContainer yourself, you can simply instantiate one:

constwinston=require('winston');const{ format}=winston;const{ combine, label, json}=format;constcontainer=newwinston.Container();container.add('category1',{format:combine(label({label:'category one'}),json()),transports:[newwinston.transports.Console({level:'silly'}),newwinston.transports.File({filename:'somefile.log'})]});constcategory1=container.get('category1');category1.info('logging to file and console transports');

Routing Console transport messages to the console instead of stdout and stderr

By default thewinston.transports.Console transport sends messages tostdout andstderr. Thisis fine in most situations; however, there are some cases where this isn't desirable, including:

  • Debugging using VSCode and attaching to, rather than launching, a Node.js process
  • Writing JSON format messages in AWS Lambda
  • Logging during Jest tests with the--silent option

To make the transport log useconsole.log(),console.warn() andconsole.error()instead, set theforceConsole option totrue:

constlogger=winston.createLogger({level:'info',transports:[newwinston.transports.Console({forceConsole:true})]});

Installation

npm install winston
yarn add winston

Run Tests

npmtest# Runs all testsnpm run test:unit# Runs all Unit tests with coveragenpm run test:integration# Runs all integration testsnpm run test:typescript# Runs tests verifying Typescript types

All of the winston tests are written withjest. Assertions use a mix ofassume and the built-in jest assertion library.


[8]ページ先頭

©2009-2025 Movatter.jp