npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crowdin/logs-formatter

v3.1.0

Published

Used in Crowdin Apps to output console logs in JSON format

Readme

Crowdin Logs Formatter

Patches the standard console methods to emit JSON records the ELK stack understands, attributes every record to the tenant that produced it, reports critical levels to Sentry, and forwards browser-side logs of Crowdin app UI modules to the server.

coverage report

Release notes and the 2.x → 3.0.0 migration guide: CHANGELOG.md.

Setup

const logsFormatter = require('@crowdin/logs-formatter');
logsFormatter.setup({ appIdentifier: 'my-app' }); // exactly once, at startup

const app = express();
app.use(logsFormatter.contextResolverMiddleware()); // must come before expressMiddleware()
app.use(express.json({ limit: '50mb' }));
app.use(logsFormatter.expressMiddleware());

For fastify:

fastify.addHook('onRequest', logsFormatter.contextResolverHook());
logsFormatter.applyFastifyRoutes(fastify);

appIdentifier is the app's identity — it becomes the app_identifier tag on every Sentry event the process reports, so even a crash outside any request says which app crashed. It is not a field of the stdout/ELK records.

Console methods modified by setup()

console.log();
console.debug();
console.info();
console.warn();
console.trace();
console.error();
console.fatal();

Context

A context exists only inside a scope, and a scope belongs to one unit of work — one request, one job, one message. Concurrent units cannot see each other's context, including in log calls that resume after an await, and nothing is left behind to mislabel a later log line.

For Express requests contextResolverMiddleware() opens the scope, for fastify the contextResolverHook() does — both fill it from the request's token (the ?jwtToken query parameter or an Authorization: Bearer header):

app.get('/work', async (req, res) => {
  await somethingSlow();
  console.log('still this request'); // stamped with this request's project/user/organization
});

For anything else — cron jobs, queue consumers, workers, scripts — open the scope yourself with runWithContext(context, fn). Everything logged inside fn, at any depth and after any await, is stamped with that context:

await logsFormatter.runWithContext(
  { project: { id: 1, identifier: 'newproject', organization_id: 200000001, user_id: 12 } },
  async () => {
    await syncProject();
    console.error('sync failed'); // stamped with the project above
  },
);

Inside a scope, add to the context with setContext({ ... }) and read it with getContext() (a read-only snapshot) or the context export. Outside any scope there is nowhere for a context to live: writes are ignored with a stderr warning naming the call site, and records simply carry no context fields. The exported Context type describes the accepted shape — worth using, because setContext() silently ignores a wrong one.

Errors

console.error(err) is enough. message keeps Class: text, the frames go to extra.backtrace, and whatever the thrower attached (code, apiError, …) goes to extra.attributes. An axios error is the exception. It keeps its bare Request failed with status code 422, with the class left on the first backtrace line. error.cause follows as caused by: lines, and several errors in one call get one record each. In the browser the injected script reports console.*, page errors and unhandled rejections. There too an error's text, stack and own properties travel as separate fields, so they land in the same message / extra.backtrace / extra.attributes split a server-side error gets.

console.error(err); // message: "CrowdinError: Language 'null' not found"

Sanitizers

The formatter masks nothing on its own. It decides what a record is made of, not what may appear in it. Masking is a sanitizer's job, and whoever uses the package registers the sanitizer.

logsFormatter.registerSanitizer((entry) => ({ ...entry, record: mask(entry.record) }));

An app built on @crowdin/app-project-module gets crowdinLogSanitizer registered for it inside configure(). That one masks by key (authorization, cookie, set-cookie, password, api_key and the like) and by the shape of a value, catching a JWT, a Bearer token, or credentials inside a URL. Using this package on its own means registering a sanitizer of your own. Without one, nothing is masked.

Write a sanitizer as a pure function. It runs once per record, not once per log call, so a call that carries two errors runs it twice, and the two calls share one rawParams array. A counter, a metric or an outbound request inside a sanitizer therefore fires as many times as the call produced records, and mutating what the sanitizer was handed applies that change on top of the previous one.

Sanitizers receive extra.attributes as an object and the formatter serialises the record only after they run, so a key-based rule reaches it. This matters most for an error carried inside another one through originalError or cause. The formatter expands that inner error in full, its HTTP config and response headers included, and the sanitizer is what decides how much of it reaches the log.

ELK attributes

While ELK supports a variety of attributes, note that not all of them are included in the current version of this formatter. Some examples of these attributes are:

"user.id", "user.login", "project.id", "project.identifier", "organization.id", "organization.domain", "client.ip" 
"user_agent.original", "http.request.referrer", "url.original", "extra.attributes", "extra.backtrace"