{"id":19974546,"url":"https://github.com/twooster/holz","last_synced_at":"2026-06-13T09:33:33.387Z","repository":{"id":57264953,"uuid":"286061188","full_name":"twooster/holz","owner":"twooster","description":"Holz: Fast, Simple, Extensible TypeScript Logger","archived":false,"fork":false,"pushed_at":"2021-07-29T07:51:22.000Z","size":307,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-23T05:36:15.417Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twooster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-08T14:43:05.000Z","updated_at":"2021-07-27T12:38:37.000Z","dependencies_parsed_at":"2022-08-25T02:20:33.392Z","dependency_job_id":null,"html_url":"https://github.com/twooster/holz","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/twooster/holz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fholz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fholz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fholz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fholz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twooster","download_url":"https://codeload.github.com/twooster/holz/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fholz/sbom","scorecard":{"id":904364,"data":{"date":"2025-08-18","repo":{"name":"github.com/twooster/holz","commit":"e81f90634a1c59c6f8a70c68edb46732053fe3f7"},"scorecard":{"version":"v5.2.1-41-g40576783","commit":"40576783fda6698350fcbbeaea760ff827433034"},"score":1.3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/27 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#sast"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"22 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-fwr7-v2mv-hh25","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-8hfj-j24r-96c4","Warn: Project is vulnerable to: GHSA-wc69-rhjr-hc9g","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-pfrx-2q88-qq97","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-px4h-xg32-q955","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T16:48:07.492Z","repository_id":57264953,"created_at":"2025-08-24T16:48:07.492Z","updated_at":"2025-08-24T16:48:07.492Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34279898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T03:15:23.419Z","updated_at":"2026-06-13T09:33:33.368Z","avatar_url":"https://github.com/twooster.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Holz Logo](./assets/holz.png \"Holz\")\n\n# Holz, a fast, minimal TypeScript logger\n\nHolz is a logging library if you want a fast, simple, JSON-to-stdout logger.\n\nIt's smaller and faster than pretty much anything out there. That's because\nit doesn't do much. There's no file rotation, there's no syslog, there's\nonly stdout. It's assumed you have an external program handling all of\nthe log file management, replication, etc.\n\nThat said, it's configurable and easy to use. Enjoy.\n## Motivation\n\nThere's a lot of loggers out there. Bunyan, Winston, Pino, log4js, ye old\nconsole.log. None of them scratched my itch:\n\n* Fast\n* Small\n* Built for Node\n* Simple, readable code\n* Minimal dependencies (only one: [`safe-json-stringify`](https://www.npmjs.com/package/safe-json-stringify))\n* TypeScript, and fully type-checked\n* Child loggers with fields\n* Splat parameters, etc\n* Custom and dynamic base objects\n* Custom log levels (w/ type checking)\n* Custom per-field formatters\n* Custom output functions\n* Custom level and message keys\n* No `v` (version), `hostname`, or `pid` fields by default; you can add them\n  if you wish, but I don't need or want them. Create your own logger factory\n  with `fields` set if you need 'em.\n* No extra cruft: No syslog, no file rotation, no stream management. Just stdout.\n\n## Use\n\n```javascript\nimport { createLogger } from 'holz'\n\nconst logger = createLogger()\n\n// Log a simple message:\nlogger.info('boo!')\n// stdout: {\"level\":\"info\",\"msg\":\"boo!\"}\n\n// Log a json object:\nlogger.info({ msg: 'oh hi!', user: 'user1' })\n// stdout: {\"level\":\"info\",\"msg\":\"oh hi!\",\"user\":\"user1\"}\n\n// Log a json object with a message, and formatted splat\nlogger.info({ user: 'user1' }, 'oh hi %s!', 'john')\n// stdout: {\"level\":\"info\",\"msg\":\"oh hi john!\",\"user\":\"user1\"}\n\n// Create a child logger\nconst childLogger = logger.child({ user: 'user1' })\nchildLogger.warn({ a: 1 }, 'oh hi %s!', 'john')\n// stdout: {\"level\":\"warn\",\"a\":1,\"msg\":\"oh hi john!\"}\n\nconst errorChildLogger = logger.child({}, { level: 'error' })\nerrorChildLogger.info(\"you won't see this message\")\n// not logged because 'info' level is lower than 'error'\n\n// Log an error (automatically transformed):\nlogger.error(new Error('Oh no!'))\n// stdout: {\"level\":\"error\",\"error\":{\"message\":\"Oh no!\",\"stack\":\"...\",...}}\n```\n\nSimple.\n\n### `createLogger`\n\nCreates a logger object. Due to TypeScript constraints, you'll want to use\nthis function to create a logger that has the appropriate types. The\noptions struct looks like this, in kinda-typescript:\n\n```javascript\ntype CreateLoggerOpts = {\n  // A structure of level name -\u003e level number, like { info: 20, warn: 30 }\n  // if this isn't provided, it defaults to bunyan/pino standard levels\n  // of { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 }\n  levels: { [k: string]: number }\n\n  // The current minimum log level\n  // Optional if `levels` is not provided; defaults to 'info'\n  // Required if `levels` is provided to set the default log level\n  level: keyof levels | number\n\n  // Optional: a function to generate the base payload object dynamically\n  base: () =\u003e Fields\n\n  // Optional: static base fields, will be applied on top of the dynamic object\n  fields: Fields\n\n  // Optional: Key in the payload that will receive the current log level,\n  // defaults to 'level'\n  levelKey: string\n\n  // Whether the numeric or string level will be put in the key, defaults to 'string'\n  levelOutput: 'string' | 'number'\n\n  // Key in the payload that will receive the log message; defaults to 'msg'\n  messageKey: string\n\n  // Optional: A function that will be called to format the message if there are\n  // any trailing parameters after the message; otherwise, this fn will not\n  // be called.\n  format: (msg: string, ...args: unknown[]) =\u003e string\n\n  // Optional: A function that will be called if there is an object passed before\n  // the message parameter when logging, to transform that object into something\n  // a bit more useful (like enumerating the fields in an error).\n  transform: (o: object) =\u003e object\n\n  // Optional: Transformations to apply to individual fields in the payload,\n  // replacing the value of given keys by the result of the supplied function\n  fieldTransforms: { [k in string | number]: (value: unknown, key: string) =\u003e unknown }\n\n  // Optional: Function that outputs a log line; defaults to writing JSON to stdout\n  output: (s: Payload) =\u003e void\n}\n```\n\nWhen you call `createLogger`, you definitely want to use a literal option struct\nso that you get TypeScript typechecking for the per-level methods attached to\nthe resulting logger. For example, this works fine:\n\n```javascript\nconst logger = createLogger({\n  levels: {\n    info: 50,\n    error: 100\n  },\n  level: 'info',\n})\n// Works fine; key retrieved from `levels` def'n\nlogger.info(...)\n```\n\nBut this does not:\n\n```javascript\nconst logOpts: any = {\n  levels: {\n    info: 50\n  },\n  level: 'info'\n}\n// Probably a typescript error:\nconst logger = createLogger(logOpts)\n// Definitely a TypeScript error:\nlogger.info(...) // `info` not defined\n```\n\nAlso, don't use the keys `child` or `setLevel` or `_log` as log levels,\nbecause that will cause problems when the logging methods are assigned to\nthe logger.\n\n### Methods on the logger\n\nThe logger has two public predefined methods on it: `child` and `setLevel`.\n\n#### `Logger#child`\n\nThis is pretty simple, it has the signature:\n\n```javascript\nLogger#child(fields, opts)\n```\n\nWhere `fields` are fields that will be applied to all messages logged\nthrough the child. These are merged with the parent logger's fields.\n`opts` is basically the same as the logger options except:\n\n1. `fields` can't be set (because that's what `fields` argument is, silly)\n1. `levels` can't be set -- they're locked in place from the parent logger\n1. `fieldTransforms` are merged with the parent field transforms\n1. `base` overrides the parent's `base` (has no merging logic)\n\nAll other fields can be set and work as expected.\n\n#### `Logger#setLevel`\n\nThis method allows you to set the log level:\n\n```javascript\nLogger#setLevel(level: string | number)\n```\n\nIf you provide a string, it had better be one of the keys of the logger's\nconfigured `levels`, otherwise this function will go boom. If you provide a\nnumber, that will work directly as the _minimum_ log level (all numeric values\nequal or higher will be printed, all lower will not be printed).\n\n#### The dynamic log methods\n\nThe logger sets up some dynamic logging methods based upon the `levels` you\nprovide. If you just want to use the default levels, the logger will have\n`trace`, `debug`, `info`, `warn`, `error` and `fatal` on it.\n\nThese functions will all have the same signature:\n\n```javascript\n(msg: string, ...fmtArgs: unknown[]) =\u003e void\n(obj: object, msg?: string, ...fmtArgs: unknown[]) =\u003e void\n```\n\nBuilding up the logging payload looks like this:\n\n1. First, the property determined by the `levelKey` setting is set on the\n   payload, with either the string or numeric level, depending on the\n   `levelOutput` setting\n1. Then the result of `base()` (if set) is merged in\n1. Then the logger's `fields` are merged in\n1. Then, if the first parameter to the log message is an object,\n   that object is passed through `transform`; the result is merged\n   into the payload\n1. Then the `messageKey` property is set on the payload; if no `fmtArgs` are\n   provided, then simply the `msg` is used, otherwise the message key is set\n   to the results of `format(msg, ...fmtArgs)`. The default `format` function\n   is `util.format`, which allows for printf-style printing.\n1. Then, if there are any field transforms, these are applied to each matching\n   named field\n1. Then two non-enumerable symbol fields are set containing the original\n   numeric and string levels (see `levelStringSym` and `levelNumberSym` exports)\n   for processing of downstream output, if you need.\n\nFinally, after all of that, the message payload is passed to the `output`\nfunction.\n\nThis whole process has a whole lot of `try`/`catch` around everything, so\nit should never blow up. Since the primary purpose of logging is to get\ninformation out of a system, the log method tries to do its best to\noutput the data you've provided, through all the transforms you've provided,\nand will generally fall back to sane defaults (e.g., untransformed fields)\nif it encounters errors.\n\n#### The defaults\n\nA few defaults are provided out of the box. These are:\n\n##### Default `format` function\n\nprintf-style msg formatting is provided by Node's\n[util.format](https://nodejs.org/api/util.html#util_util_format_format_args).\n\n##### Default `output` function\n\nThat looks like this:\n\n```javascript\nimport safeStringify = require('safe-json-stringify')\n\nexport const defaultOutput =\n  (p: Payload) =\u003e process.stdout.write(safeStringify(p) + \"\\n\")\n```\n\nIf you want to use your own, you can re-import `safeStringify` from\nthis lib to access that dependency.\n\n##### Default `transform` function\n\nThat looks like this:\n\n```javascript\nexport function defaultTransform(o: object) {\n  return o instanceof Error ? { error: transformError(o) } : o\n}\n```\n\n`transformError` basically takes any Error-alike object (has a `.stack`\nproperty), and makes it into something a bit more palatable to\n`JSON.stringify`. (It also unwinds the error `cause` if you're using\n[verrors](https://github.com/joyent/node-verror).)\n\nYou can import `transformError` if you like and build your own transform\nfunction -- like maybe detecting and formatting HTTP Request/Response objects.\nSince Error logging is the only thing that is a greatest-common-denominator\nacross all use-cases, it's the only thing intended to be supported here.\n\n##### Default `levels`\n\nThese follow the Bunyan/Pino defaults:\n\n```javascript\nexport const defaultLevels = {\n  trace: 10,\n  debug: 20,\n  info:  30,\n  warn:  40,\n  error: 50,\n  fatal: 60,\n} as const\n```\n\n##### Default `levelOutput`\n\nAgain, this setting determines whether numeric or string levels are output\nin the field determined by the `levelKey` setting. It defaults to:\n\n```javascript\nexport const defaultLevelOutput = 'string' as const\n```\n\nThis means that the level information is outputted as a string, e.g.,\n`'info'` or `'error'`.\n\n##### Default `levelKey`\n\nAs you might expect, the default key used on the payload output to contain\nlog-level information is:\n\n```javascript\nexport const defaultLevelKey = 'level'\n```\n\nAnother good option might be `'severity'`, but `'level'` is the default setting.\n\n\n##### Default `messageKey`\n\nWhat field on the payload object receives the provided message (if any):\n\n```javascript\nexport const defaultMessageKey = 'msg'\n```\n\n##### Default `level`\n\n```javascript\nexport const defaultLevel = defaultLevels['info']\n```\n\nThe default log level is `'info'`.\n\n##### Default `fieldTransforms`\n\n```javascript\nexport const defaultFieldTransforms = {\n  err: transformError,\n  error: transformError,\n}\n```\n\nAgain, as a greatest-common-denominator, we provide some field transforms on\n`err` and `error` to help capture stack traces, etc.\n\n### Creating your own logger factory\n\nThe easiest thing to do is to simply provide some defaults to `createLogger`.\nOtherwise, to get the correct type-checking, you'll need to mix together the\n`BaseLogger` class with the `LogMethods` type to get your proper type\nchecking. Check out how `createLogger` works to see that in action.\n\n## I want microbenchmarks:\n\n**Updated 2021-07-27:**\n\nSure ok, synchronous streaming to /dev/null, if you care about that sort of\nthing:\n\n```\n          1,010,000 ops/sec \u003e basic#holz (4.49x)\n            713,000 ops/sec \u003e basic#pino (3.16x)\n            872,000 ops/sec \u003e basic#bole (3.87x)\n            282,000 ops/sec \u003e basic#bunyan (1.25x)\n            226,000 ops/sec \u003e basic#winston (1x)\n\n  Benches: 5\n  Fastest: basic#holz\n  Elapsed: 27.1s\n\n\n            713,000 ops/sec \u003e child#holz (3.76x)\n            888,000 ops/sec \u003e child#pino (4.69x)\n            262,000 ops/sec \u003e child#bunyan (1.38x)\n            189,000 ops/sec \u003e child#winston (1x)\n\n  Benches: 4\n  Fastest: child#pino\n  Elapsed: 21.9s\n\n\n            557,000 ops/sec \u003e child child#holz (3.56x)\n            893,000 ops/sec \u003e child child#pino (5.71x)\n            232,000 ops/sec \u003e child child#bunyan (1.48x)\n            156,000 ops/sec \u003e child child#winston (1x)\n\n  Benches: 4\n  Fastest: child child#pino\n  Elapsed: 21.9s\n\n\n            516,000 ops/sec \u003e dynamic child#holz (2.66x)\n            461,000 ops/sec \u003e dynamic child#pino (2.37x)\n            194,000 ops/sec \u003e dynamic child#bunyan (1x)\n            195,000 ops/sec \u003e dynamic child#winston (1.01x)\n\n  Benches: 4\n  Fastest: dynamic child#holz\n  Elapsed: 22s\n\n\n            368,000 ops/sec \u003e dynamic child child#holz (8.74x)\n             42,000 ops/sec \u003e dynamic child child#pino (1x)\n            143,000 ops/sec \u003e dynamic child child#bunyan (3.41x)\n             96,300 ops/sec \u003e dynamic child child#winston (2.29x)\n\n  Benches: 4\n  Fastest: dynamic child child#holz\n  Elapsed: 22s\n```\n\nWhat does it mean? What's the difference between `child` and `dynamic child`?\n\n`child`: creates a child logger _once_, before the benchmark, and then logs to\nit for each execution.\n\n`dynamic-child`: for _each execution_, creates a child logger and then logs to\nit.\n\nI prefer to optimize for the latter case, since it represents how I suspect most\npeople use child-loggers: attach a bit of context (e.g., for a web request), do\na bit of logging, and then throw it away. Pino is particularly slow for this\ntype of logging, especially nested more than once: you pay a large cost\nfor instantiating a child logger, and a lower cost for each logging call.\n\nNote that Winston has a terrible memory leak and will crash the benchmark\nsuites if run all together. Sadface.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwooster%2Fholz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwooster%2Fholz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwooster%2Fholz/lists"}