Update dependency @larvit/log to v2.4.0 #136
Reference in New Issue
Block a user
Delete Branch "renovate/larvit-log-2.x"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
2.3.0→2.4.0Release Notes
larvit/log (@larvit/log)
v2.4.0Compare Source
An export queue that batches, retries and can hold records and spans across an app restart;
fewer ways for a credential to reach a span or
stderr, with some still open;formattaking aformatter function; and
log.enabled, theLoggertype, theclockoption and an honouredunsampled
traceparent. A child no longer inherits its parent'straceparent, andentryFormatterand the level-string shorthand are deprecated.Read Security first: several entries ask you to rotate credentials v2.3.0 exported, and some
exposures are still open.
Security
spanNameand other text you write is exported aswritten. Goals now say plainly which text this library will never clean for you: the log message,
metadata,
context,spanName, and a header or query value you allow-list that simply is asecret. Nothing exported has changed —
spanNamehas always gone out as you wrote it — but ifyou build one from a request url (
spanName: "GET " + req.url), its credentials reach yourtracing backend, in the span name, the scope name and, under
printTraceInfo, your console; achild log inherits the name, so one such
spanNamelabels the whole trace. Name the route, notthe url.
log.fetchstill exports credentials in a url nested in the request path, inurl.full. Not fixed, andexporting on every such call until it is:
url.fullis built from the origin and the path, and onlythe query is redacted, so
log.fetch("https://proxy.test/fetch/https://user:pass@cb.test/x")— the shape a fetch-throughproxy, a CORS or image proxy or a webhook replay endpoint takes — exports that password to your
tracing backend with no capture option involved, which means you cannot rule yourself out by
reading your capture config. Percent-encoding it changes nothing. It is deferred rather than
unfixable: a path is not a value, so neither redaction rule in the next bullet transfers cleanly.
Rotate any credential you have passed inside a url nested in a path. The
@,%40and%2540search in the next bullet finds its userinfo. Its query — where a signed url keepsX-Amz-Signature,sigoraccess_token— reaches the path only percent-encoded, so also searchurl.fullfor%3Fand%253F, upper or lower case, and foraHR0c, which begins abase64-encoded url. Rotate any token a hit holds; a SigV4 one not base64-encoded also holds
aws4_requestand follows the presigned-url bullet below,captureQueryor not.log.fetchfirst exportedurl.fullin v2.3.0, so no older span carries it.REDACTED.A header you allow-list is no longer a way to export a credential:
authorization,proxy-authorization,cookieandset-cookienamed incaptureRequestHeadersorcaptureResponseHeadersrecordREDACTED, so the span still shows the header was there. Anyother captured header value records
REDACTEDtoo where it holds url userinfo — arefereror alocationcarrying an OAuthredirect_uri— and withcaptureQueryon so does a query value,where matching only the key left
?next=https://user:pass@host/xexporting the password, as doesa query key, so a credentialed url written as a bare key records as a parameter named
REDACTED.It sees through one layer of percent-encoding but not two; a second layer still gets past it. A
value merely shaped like a credential goes the same way: a
locationofhttps://cdn.test//logo@2x.pngrecordsREDACTEDwhole. What it does not reach is a header orquery value that simply is a secret —
x-api-key, your own signed token — which is exported asyou sent it, so don't allow-list one.
Rotate any credential you named one of those four headers for, or put in a url you captured in
a header or a query string: search each header you allow-listed, under
http.request.header.*andhttp.response.header.*, for those four names, and search those sameattributes and
url.fullfor@,%40or%2540.log.fetch, both allow-lists andcaptureQueryfirst shipped in v2.3.0, so a span an older version exported holds none of theseattributes.
REDACTED. WithcaptureQueryon, a presigned SigV4 url — S3 or any S3-compatible store — exported itsX-Amz-Signature, the access key id inX-Amz-Credentialand the session token inX-Amz-Security-Tokeninurl.full; a GCS url exported the service account's email inX-Goog-CredentialorGoogleAccessId, its signature already redacted. All five now recordREDACTED; every other parameter is kept. A GCS url needs nothing: an email leaked and nosignature did. On v2.3.0 or later with
captureQueryon, act on every leaked SigV4 url that hasnot expired: it is replayable until its
X-Amz-DateplusX-Amz-Expires, both still inurl.full, has passed, and the signature, key id and session token alone reveal no secret, so anexpired one needs nothing. Search
url.fullforaws4_request, which every leakedX-Amz-Credentialends in and a redacted one never holds; on S3 itself no presigned url outlivesseven days, so only the last week's spans can hold a live one. For a live hit, a credential
starting
ASIAis temporary, and the url died with that session whateverX-Amz-Expiressays:revoke the role's active sessions only if it may still be open. Any other is a long-term key:
rotate it.
is exported as
http://REDACTED@host/x. On Node and in browsersfetchrefuses a url carryingcredentials and quotes the whole url into its
TypeError, which reached the backend both as thelog.fetchspan's own status and, once you caught that rejection and forwarded it, asend({ error })on the span around it. The rejection reaching the caller is unchanged;log.span.status.messagenow shows the redacted text, anderror.typeis untouched. Nothing torotate: no released version exported a span status message at all —
end()took no argument andOtlpSpan.statushad nomessage— so both routes exist only alongside their own redaction.log.fetchof auser:pass@url still hands the credentials to theplatform. Not fixed: on React Native, whose
fetchis anXMLHttpRequestpolyfill,log.fetch("https://user:pass@host/x")reaches the platform with the credentials still in theurl, where Node and browsers refuse it outright. On iOS the URL loading system answers the
server's
WWW-Authenticatechallenge with them, so they go on the wire; on Android OkHttp sendsno credentials and hands you the 401, except through a plain-
http:proxy, whose request linecarries the whole url.
log.fetchmirrors whatever the runtime does, so it cannot close this.Nothing about it reaches your tracing backend —
url.fullnever holds the outer url's userinfo —so there is nothing to rotate on account of this platform difference; the exposure is the network path to the host, in
the clear if that url is
http:. Pass anAuthorizationheader, and strip userinfo from a urlyou did not build.
log.fetchtraces only a url that resolves tohttp:orhttps:. Anything else is fetcheduntraced — no span, and no
traceparentsent. It used to export a span whoseurl.fullheldwhatever the url did: written without
//, a url parses to an opaque path, solog.fetch("myapp:user:pass@host/x")exportednulluser:pass@host/xand adata:url exportedits whole payload. If you have passed credentials or private data in such a url, rotate them:
search your tracing backend for spans whose
url.fullstarts withnullor matcheshttps?://[^/]*https?:.*, which finds ablob:url'shttps://example.comhttps://example.com/uuid.log.fetchfirst exportedurl.fullin v2.3.0, so no older span carries it.otlpHttpBaseURIno longer reachstderr, and basic auth works.fetchrejectsa
user:pass@url outright on Node and in browsers, and that rejection quoted the whole url —credentials included — into the error line of every failed export.
user:pass@is now sent as anAuthorization: Basicheader, percent-decoded, and the request url carries none.If you have ever set
user:pass@inotlpHttpBaseURI, rotate those credentials: they are inwhatever collects your
stderr, findable by searching it for your collector's hostname.Percent-encode any
/ ? #in a password, and a literal%as%25.log.confandqueue.confhold your OTLP credentials as written.queue.confholdsotlpHttpBaseURI,user:pass@included, andotlpAdditionalHeaders, a bearer token included, exactly as you gave them, andlog.confholdsthem too, whether you set them on
Logor on theQueueyou passed asotlpQueue. Logging,serialising or sending either
confputs the credentials wherever it lands. Don't log aconf.If you ever have, rotate those credentials: search where it landed for
otlpHttpBaseURIandotlpAdditionalHeaders, which every serialisedconfholding them carries.confwhoseQueueusesstorage: localStoragecarries everylocalStorage entry of the origin. Serialising either
confwrites them under"storage".If you have serialised one, rotate any session token it carries: search where a
conflanded for"storage":.Everything else
traceparent. Fornew Log({ parentLog }),child.conf.traceparentisundefinedunless you pass one, as a clone's already was. Read theincoming header from the parent's
conf, or propagate withlog.traceparent(). The child's spanwas never affected.
logLevellogs atinfoand warns, instead of throwing. AlogLevelthat is not alevel —
LOG_LEVEL=tracefrom pino,httpfrom winston — threw from every level method. It logs at"info"and writes onewarnline perstderrsinknaming the value.
round was in flight scheduled a batch send of its own that the retry backoff did not take over,
so the process stayed alive for up to
batchDelayMsafterawait log.flush()orawait log.end()had already returned.url.full. WithcaptureQueryon, a repeatedknown-sensitive key —
?Signature=a&Signature=b— used to collapse to oneREDACTED.otlpHttpBaseURIthat is nothttp:orhttps:throws in the constructor.otlp:collector.example.comused to build a queue that could never export. Written without//, such a URI parses to an opaque path, which put anyuser:pass@in it straight back intothe reported url.
otlpAdditionalHeaderswins over the queue's own, and is read on every send. Itreplaces the one the queue sets itself whatever its casing, and is read afresh on each send, so a rotated token takes effect. A name or value the runtime
rejects drops that batch, reported as
OTLP export headers invalid, batch droppednaming theheader, never its value.
formattakes a formatter function, and theentryFormatteroption is deprecated. It is(entry) => string, andformatis what children and clonesinherit. New export:
EntryFormatter.The
entryFormatteroption is deprecated: it still formats and still wins over a"text"/"json"format, writes onewarnline perstderrsink for each distinct warningtext whatever
logLevelsays, and 3.0.0 removes it. Two different formatters, one per spelling,throw.
log.conf.entryFormatteris deprecated and non-enumerable.{ ...log.conf }andObject.keys(log.conf)no longer carry it, so a spread carries at most a"text"/"json"format, never the function formatter — useclone(). 3.0.0 removes it, andlog.conf.formatholds a function from then. Until then keep reading it: it reads the formatter in use, writing it
swaps it, and it wins over
log.conf.formatas in v2.3.0.log.conf.formatnever holds afunction; it reads
undefinedafterformat: fn.ResolvedLogConfstill declares it, soconst c: ResolvedLogConf = { ...log.conf }compilesand
c.entryFormatter(entry)throws at runtime.JSON.stringify(log.conf)carriesformatand, with OTLP, the queue'sconf. It carriesformat,"text"by default, where v2.3.0 left the keyabsent unless you passed one — and omits it when you passed a function, as it omits any function.
With OTLP configured it carries a
QueueinotlpQueue— the oneotlpHttpBaseURIbuildsincluded — as its
conf, so dropping the top-levelotlp*keys no longer keeps the credentials out.Not promised; see below.
formatset on a child now applies. Before, on a child (parentLog), the parent's resolved formattersilently kept winning.
log.conf.formatis read where a line is written, so writing it swapsthe formatter on a live instance that has no function formatter.
JSON.stringifyoflog.confcarries is outside semver; copy settings withclone(). It may change in a minor.@larvit/log: ….new Log("debug")andlog.clone("debug"), is deprecated. It stillsets the level, writes one
warnline perstderrsink for each distinct warning text,whatever
logLevelsays, and 3.0.0 removes it. Pass{ logLevel }instead.clockoption onLogandQueue, so a test drives time.{ now, setTimeout, clearTimeout }behind every span andrecord timestamp and behind the queue's batch, retry and send-timeout timers. Defaults to the system clock, inherited by children and clones.
New exports:
Clock,TimerHandle.clone()leave the options object they are given untouched. They nolonger write defaults, inherited settings or the built
Queueinto the options object they are given, so one object reused for several instances no longer
makes them share a queue.
traceparentwith the sampled flag off is honoured. The instance and its childrenexport no spans, and
log.traceparent()andlog.fetchpass00downstream. Log records stillexport. New
log.sampledfield, onLogInt;parseTraceparentreturnssampledand rejects version
ff.{}or{"partialSuccess":{}}wasreported as a rejection. A
partialSuccesswith a rejected count is reported throughreportas
OTLP export partially rejected, withrejectedand the collector'serrorMessageaserror.colors: falseturns off the ANSI colour codes in text output. Unset in code,NO_COLOR(non-empty) turns it off; otherwise
FORCE_COLORturns it on, except0orfalsewhich turnit off. A value set in code wins over both. Inherited by children and clones. Formatters receive the setting as
EntryFormatterConf.colors;msgTextFormattercolours unless it is
false.sent with
keepaliveand retried with backoff on a network error, timeout, 408, 429 or 5xx; othernon-2xx drops the batch. One stderr line per failed attempt. Bounded at 1000 items, oldest dropped
and the count reported once.
otlpHttpBaseURIbuilds the defaultQueue, read back aslog.conf.otlpQueueand shared bychildren and clones;
otlpQueuetakes your own, e.g.new Queue({ otlpHttpBaseURI, storage: AsyncStorage })to survive an app restart.log.flush()delivers without ending;end()flushes after closingthe span. New exports:
Queue,OtlpQueue,OtlpPayload,QueueConf,ResolvedQueueConf,QueueStorage.end({ error })marks the instance's span failed. StatusERRORwith the error's message, and anerror.typespan attribute from the error's stringcode, elsename, else"_OTHER".log.error()does not mark thespan.
OtlpSpan.statusgains an optionalmessage. Alog.fetchspan that failed with a thrownerror now carries the same status message.
contextacceptundefinedvalues, and drop those keys.Log's andLogger's level methods andcontextaccept them, typed by the newMetadataInput;LogInt's keepMetadatauntil 3.0.0. Such keys are dropped from console,custom-formatter and OTLP output, so
{ port: options.port }with an optional field type-checks.New export:
LogOptions, whatnew Log()andclone()take.LogConfkeepscontextasMetadata, andlog.conf.contextreads back with those keys already dropped, so aLogConfyou built for v2.3.0 still constructs and a read of
log.conf.contextstill compiles.MetadataandMetadataValueare unchanged.log.enabled(level), so a caller skips building metadata nobody will see.truewhen a call at that level would output;falsefor a level it does not know.Loggertype for a library that accepts a logger. The six level methods plusenabled. Libraries acceptLogger;parentLogtakesLogInt, which gainsenabled,flushandsampledas optional members, so aLogIntyou wrote against v2.3.0 still compiles. 3.0.0 makes itLoggerplus the rest, thosethree required.
01. Match this library's spans on thetelemetry.sdk.nameresource attribute,@larvit/log.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate CLI.
1bf0a99494todd910feeb8dd910feeb8to106efd14baView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.