{"id":18998729,"url":"https://github.com/foxxmd/logging","last_synced_at":"2025-04-22T14:42:59.654Z","repository":{"id":224115185,"uuid":"762458485","full_name":"FoxxMD/logging","owner":"FoxxMD","description":"A typed, opinionated, batteries-included, Pino-based logging solution for backend TS/JS projects","archived":false,"fork":false,"pushed_at":"2024-10-28T18:08:43.000Z","size":2672,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-17T04:18:56.040Z","etag":null,"topics":["child-logger","logging","logging-library","nodejs","pinojs","typescript-library"],"latest_commit_sha":null,"homepage":"https://foxxmd.github.io/logging","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FoxxMD.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-23T20:31:16.000Z","updated_at":"2024-10-28T17:55:44.000Z","dependencies_parsed_at":"2024-10-28T18:48:08.035Z","dependency_job_id":null,"html_url":"https://github.com/FoxxMD/logging","commit_stats":null,"previous_names":["foxxmd/logging"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FoxxMD%2Flogging","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FoxxMD%2Flogging/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FoxxMD%2Flogging/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FoxxMD%2Flogging/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FoxxMD","download_url":"https://codeload.github.com/FoxxMD/logging/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250261417,"owners_count":21401485,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["child-logger","logging","logging-library","nodejs","pinojs","typescript-library"],"created_at":"2024-11-08T17:47:45.771Z","updated_at":"2025-04-22T14:42:59.617Z","avatar_url":"https://github.com/FoxxMD.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @foxxmd/logging\n\n[![Latest Release](https://img.shields.io/github/v/release/foxxmd/logging)](https://github.com/FoxxMD/logging/releases)\n[![NPM Version](https://img.shields.io/npm/v/%40foxxmd%2Flogging)](https://www.npmjs.com/package/@foxxmd/logging)\n[![Try on Runkit](https://img.shields.io/static/v1?label=Try%20it%20online%20on\u0026message=RunKit\u0026color=f55fa6)](https://npm.runkit.com/%40foxxmd%2Flogging)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA typed, opinionated, batteries-included, [Pino](https://getpino.io)-based logging solution for backend TS/JS projects.\n\nFeatures:\n\n* Fully typed for Typescript projects\n* One-line, [turn-key logging](#quick-start) to console and rotating file\n* [Child (nested) loggers](#child-loggers) with hierarchical label prefixes for log messages\n* Per-destination level filtering configurable via ENV or arguments\n* Clean, opinionated log output format powered by [pino-pretty](https://github.com/pinojs/pino-pretty):\n  * Colorized output when stream is TTY and supports it\n  * Automatically [serialize passed objects and Errors](#serializing-objects-and-errors), including [Error Cause](https://github.com/tc39/proposal-error-cause)\n  * Automatically redact current working directory from log output\n* [Bring-Your-Own settings](#additional-app-logger-configuration)\n  * Add or use your own streams/[transports](https://getpino.io/#/docs/transports?id=known-transports) for destinations\n  * All pino-pretty configs are exposed and extensible\n* [Build-Your-Own Logger](#building-a-logger)\n  * Don't want to use any of the pre-built transports? Leverage the convenience of @foxxmd/logging wrappers and default settings but build your logger from scratch\n\n\u003cimg src=\"/assets/example.svg\" alt=\"example log output\"\u003e\n\n**Documentation best viewed on [https://foxxmd.github.io/logging](https://foxxmd.github.io/logging)**\n\n# Install\n\n```\nnpm install @foxxmd/logging\n```\n\n# Quick Start\n\n```ts\nimport { loggerAppRolling, loggerApp } from \"@foxxmd/logging\";\n\nconst logger = loggerApp();\nlogger.info('Test');\n/*\n * Logs to -\u003e console, colorized\n * Logs to -\u003e CWD/logs/app.log\n * \n * [2024-03-07 10:31:34.963 -0500] DEBUG: Test\n * */\n\n\n// or for rolling log files we need to scan logs dir before opening a file\n// and need to await initial logger\nconst rollingLogger = await loggerAppRolling();\nrollingLogger.info('Test');\n/*\n * Logs to -\u003e console, colorized\n * Logs to daily log file, max 10MB size -\u003e CWD/logs/app.1.log\n * \n * [2024-03-07 10:31:34.963 -0500] DEBUG: Test\n * */\n```\n\n# Loggers\n\nThe package exports 4 top-level loggers.\n\n### App Loggers\n\nThese are the loggers that should be used for the majority of your application. They accept an optional configuration object for configuring log destinations.\n\n* [`loggerApp`](https://foxxmd.github.io/logging/functions/index.loggerApp.html) - Logs to console and a fixed file destination\n* [`loggerAppRolling`](https://foxxmd.github.io/logging/functions/index.loggerAppRolling.html) - Logs to console and a rolling file destination\n\n### Helper Loggers\n\nThese loggers are pre-defined for specific use cases:\n\n* [`loggerDebug`](https://foxxmd.github.io/logging/variables/index.loggerDebug.html) - Logs ONLY to console at minimum `debug` level. Can be used during application startup before a logger app configuration has been parsed.\n* [`loggerTest`](https://foxxmd.github.io/logging/variables/index.loggerTest.html) - A noop logger (will not log anywhere) for use in tests/mockups.\n\n## Configuring\n\nThe [App Loggers](#app-loggers) take an optional [`LogOptions`](https://foxxmd.github.io/logging/interfaces/index.LogOptions.html) to configure [`LogLevel`](https://foxxmd.github.io/logging/types/index.LogLevel.html) globally or individually for **Console** and **File** outputs. [`file` in `LogOptions`](https://foxxmd.github.io/logging/interfaces/index.FileLogOptions.html) may also be an object that specifies more behavior for log file output.\n\n```ts\nconst infoLogger = loggerApp({\n level: 'info' // console and file will log any levels `info` and above\n});\n\nconst logger = loggerApp({\n  console: 'debug', // console will log `debug` and higher\n  file: 'warn' // file will log `warn` and higher\n});\n\nconst fileLogger = loggerRollingApp({\n  // no level specified =\u003e console defaults to `info` level\n  file: {\n      level: 'warn', // file will log `warn` and higher\n      path: '/my/cool/path/output.log', // output to log file at this path\n      frequency: 'daily', // rotate hourly\n      size: '20MB', // rotate if file size grows larger than 20MB\n      timestamp: 'unix' // use unix epoch timestamp instead of iso8601 in rolling file\n  }\n});\n```\n\nAn optional second parameter, [`LoggerAppExtras`](https://foxxmd.github.io/logging/interfaces/index.LoggerAppExtras.html), may be passed that allows adding additional log destinations or pino-pretty customization to the [App Loggers](#app-loggers). Some defaults and convenience variables for pino-pretty options are also available in [`@foxxmd/logging/factory`]((https://foxxmd.github.io/logging/modules/factory.html)) prefixed with `PRETTY_`.\n\nAn example using `LoggerAppExtras`:\n```ts\nimport { loggerApp } from '@foxxmd/logging';\nimport {\n    PRETTY_ISO8601,\n    buildDestinationFile \n} from \"@foxxmd/logging/factory\";\n\n// additional file logging but only at `warn` or higher\nconst warnFileDestination = buildDestinationFile('warn', {path: './myLogs/warn.log'});\n\nconst logger = loggerApp({\n  level: 'debug', // console AND built-in file logging will log `debug` and higher\n  }, {\n   destinations: [warnFileDestination],\n   pretty: {\n     translateTime: PRETTY_ISO8601 // replaces standard timestamp with ISO8601 format\n   }\n});\nlogger.debug('Test');\n// [2024-03-07T11:27:41-05:00] DEBUG: Test\n```\n\nSee [Building A Logger](#building-a-logger) for more information.\n\n#### Colorizing Docker Logs\n\nColor output to STD out/err is normally automatically detected by [colorette](https://github.com/jorgebucaran/colorette) or can manually be set using `colorize` anywhere [PrettyOptions](https://foxxmd.github.io/logging/interfaces/factory._internal_.PrettyOptions_.html) are accepted. However docker output can be hard to detect as supporting colorizing, or the output may not be TTY at the container interface but is viewed by a terminal or web app that does support colorizing.\n\nTherefore `@foxxmd/logging` will look for a `COLORED_STD` environmental variable and, if no other `colorize` option is set _and the ENV is not empty_, will use the truthy value of this variable to set `colorize` **for any `buildDestinationStdout` or `buildDestinationStderr` transports.** This includes the built-in stdout transports for `loggerApp` and `loggerAppRolling`.\n\nThus you could set `COLORED_STD=true` in your Dockerfile to coerce colored output to docker logs. If a user does not want colored output for any reason they can simply override the environmental variable like `COLORED_STD=false`\n\n## Usage\n\n### Child Loggers\n\n[Pino Child loggers](https://getpino.io/#/docs/child-loggers) can be created using the [`childLogger`](https://foxxmd.github.io/logging/functions/index.childLogger.html) function with the added ability to inherit **Labels** from their parent loggers.\n\n**Labels** are inserted between the log level and message contents of a log. The child logger inherits **all** labels from **all** its parent loggers.\n\n`childLogger` accepts a single string label or an array of string labels.\n\n```ts\nimport {loggerApp, childLogger} from '@foxxmd/logging';\n\nlogger = loggerApp();\nlogger.debug('Test');\n// [2024-03-07 11:27:41.944 -0500] DEBUG: Test\n\nconst nestedChild1 = childLogger(logger, 'First');\nnestedChild1.debug('I am nested one level');\n// [2024-03-07 11:27:41.945 -0500] DEBUG: [First] I am nested one level\n\nconst nestedChild2 = childLogger(nestedChild1, ['Second', 'Third']);\nnestedChild2.warn('I am nested two levels but with more labels');\n// [2024-03-07 11:27:41.945 -0500] WARN: [First] [Second] [Third] I am nested two levels but with more labels\n\nconst siblingLogger = childLogger(logger, ['1Sib','2Sib']);\nsiblingLogger.info('Test');\n// [2024-03-07 11:27:41.945 -0500] INFO: [1Sib] [2Sib] Test\n```\n\nLabels can also be added at \"runtime\" by passing an object with `labels` prop to the logger level function. These labels will be appended to any existing labels on the logger.\n\n```ts\nlogger.debug({labels: ['MyLabel']}, 'My log message');\n```\n\n### Serializing Objects and Errors\n\nPassing an object or array as the first argument to the logger will cause the object to be JSONified and pretty printed below the log message\n\n```ts\nlogger.debug({myProp: 'a string', nested: {anotherProps: ['val1', 'val2'], boolProp: true}}, 'Test');\n /*\n[2024-03-07 11:39:37.687 -0500] DEBUG: Test\n  myProp: \"a string\"\n  nested: {\n    \"anotherProps\": [\n      \"val1\",\n      \"val2\"\n     ],\n   \"boolProp\": true\n  }\n  */\n```\n\nPassing an `Error` as the first argument will pretty print the error stack including any [causes.](https://github.com/tc39/proposal-error-cause)\n\n```ts\nconst er = new Error('This is the original error');\nconst causeErr = new ErrorWithCause('A top-level error', {cause: er});\nlogger.debug(causeErr, 'Test');\n/*\n[2024-03-07 11:43:27.453 -0500] DEBUG: Test\nError: A top-level error\n    at \u003canonymous\u003e (/my/dir/src/index.ts:55:18)\ncaused by: Error: This is the original error\n    at \u003canonymous\u003e (/my/dir/src/index.ts:54:12)\n */\n```\n\nPassing an `Error` without a second argument (message) will cause the top-level error's message to be printed instead of log message.\n\n# Building A Logger\n\nAll the functionality required to build your own logger is exported by [`@foxxmd/logging/factory`](https://foxxmd.github.io/logging/modules/factory.html). You can customize almost every facet of logging.\n\nA logger is composed of a minimum default level and array of objects that implement [`StreamEntry`](https://foxxmd.github.io/logging/interfaces/index._internal_.StreamEntry-1.html), the same interface used by [`pino.multistream`](https://getpino.io/#/docs/api?id=pino-multistream). The only constraint is that your streams must accept the same levels as `@foxxmd/logging` using the [`LogLevelStreamEntry`](https://foxxmd.github.io/logging/types/index.LogLevelStreamEntry.html) interface that extends `StreamEntry`.\n\n```ts\nimport {LogLevelStreamEntry} from '@foxxmd/logging';\nimport { buildLogger } from \"@foxxmd/logging/factory\";\n\nconst myStreams: LogLevelStreamEntry[] = [];\n// build streams\n\nconst logger = buildLogger('debug', myStreams);\nlogger.debug('Test');\n```\n\n`factory` exports several \"destination\" `LogLevelStreamEntry` function creators with default configurations that can be overridden.\n\n```ts\nimport {\n    buildLogger,\n    buildDestinationStream,     // generic NodeJS.WriteableStream or SonicBoom DestinationStream\n    buildDestinationStdout,     // stream to STDOUT\n    buildDestinationStderr,     // stream to STDERR\n    buildDestinationFile,       // write to static file\n    buildDestinationRollingFile // write to rolling file\n} from \"@foxxmd/logging/factory\";\n```\n\nAll `buildDestination` functions take args:\n\n* `level` (first arg) - minimum level to log at\n* `options` (second arg) - an object extending [`pino-pretty` options](https://github.com/pinojs/pino-pretty?tab=readme-ov-file#options), [`PrettyOptions`](https://foxxmd.github.io/logging/interfaces/factory._internal_.PrettyOptions_.html)\n\n`options` inherits a default `pino-pretty` configuration that comprises `@foxxmd/logging`'s opinionated logging format. The common default config can be generated using [`prettyOptsFactory`](https://foxxmd.github.io/logging/functions/factory.prettyOptsFactory.html) which accepts an optional `PrettyOptions` object to override defaults:\n\n```ts\nimport { prettyOptsFactory } from \"@foxxmd/logging/factory\";\n\nconst defaultConfig = prettyOptsFactory();\n\n// override with your own config\nconst myCustomizedConfig = prettyOptsFactory({ colorize: false });\n```\n\nPre-configured `PrettyOptions` are also provided for different destinations:\n\n```ts\nimport {\n  PRETTY_OPTS_CONSOLE, // default config\n  PRETTY_OPTS_FILE     // disables colorize\n} from \"@foxxmd/logging/factory\";\n```\n\nSpecific buildDestinations also require passing a stream or path:\n\n[`buildDestinationStream`](https://foxxmd.github.io/logging/functions/factory.buildDestinationStream.html) must pass a `NodeJS.WriteableStream` or SonicBoom `DestinationStream` to options as [`destination`](https://foxxmd.github.io/logging/types/factory.StreamDestination.html)\n\n```ts\nimport {buildDestinationStream} from \"@foxxmd/logging/factory\";\n\nconst myStream = new WritableStream();\nconst dest = buildDestinationStream('debug', {destination: myStream});\n```\n\n[`buildDestinationStdout`](https://foxxmd.github.io/logging/functions/factory.buildDestinationStdout.html) and [`buildDestinationStderr`](https://foxxmd.github.io/logging/functions/factory.buildDestinationStderr.html) do not require a destination as they are fixed to STDOUT/STDERR\n\n[`buildDestinationFile`](https://foxxmd.github.io/logging/functions/factory.buildDestinationFile.html) and [`buildDestinationRollingFile`](https://foxxmd.github.io/logging/functions/factory.buildDestinationRollingFile.html) must pass a [`path`](https://foxxmd.github.io/logging/types/factory.FileDestination.html) to options\n\n```ts\nimport {buildDestinationFile} from \"@foxxmd/logging/factory\";\n\nconst dest = buildDestinationFile('debug', {path: '/path/to/file.log'});\n```\n\n### Example\n\nPutting everything above together\n\n```ts\nimport {\n  buildDestinationStream,\n  buildDestinationFile,\n  prettyOptsFactory,\n  buildDestinationStdout,\n  buildLogger\n} from \"@foxxmd/logging/factory\";\nimport { PassThrough } from \"node:stream\";\n\nconst hookStream = new PassThrough();\nconst hookDestination = buildDestinationStream('debug', {\n  ...prettyOptsFactory({sync: true, ignore: 'pid'}),\n  destination: hookStream\n});\n\nconst debugFileDestination = buildDestinationFile('debug', {path: './myLogs/debug.log'});\nconst warnFileDestination = buildDestinationFile('warn', {path: './myLogs/warn.log'});\n\nconst logger = buildLogger('debug', [\n  hookDestination,\n  buildDestinationStdout('debug'),\n  debugFileDestination,\n  warnFileDestination\n]);\nhookStream.on('data', (log) =\u003e {console.log(log)});\nlogger.debug('Test')\n// logs to hookStream\n// logs to STDOUT\n// logs to file ./myLogs/debug.log\n// does NOT log to file ./myLogs/warn.log\n```\n\n### Parsing LogOptions\n\nIf you wish to use [`LogOptions`](#configuring) to get default log levels for your destinations use [`parseLogOptions`](https://foxxmd.github.io/logging/functions/index.parseLogOptions.html):\n\n```ts\nimport {parseLogOptions, LogOptions} from '@foxxmd/logging';\n\nconst parsedOptions: LogOptions = parseLogOptions(myConfig);\n```\n\n# Examples\n\nVarious use-cases for `@foxxmd/logging` and how to configure a logger for them.\n\nRemember, `loggerApp` and `loggerAppRolling` **accept the same arguments.** The examples below use `loggerApp` but `loggerAppRolling` can be used as a drop-in replacement in order to use a rolling log file.\n\n#### Log to Console and File\n\n```ts\nimport {loggerApp, loggerAppRolling} from '@foxxmd/logging';\n\n// static log file at ./logs/app.log\nconst staticLogger = loggerApp();\n\n// rolling log file at ./logs/app.1.log\nconst rollingLogger = loggerAppRolling();\n```\n\n#### Log At Specific Level Or Higher for Console and File\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\n\n// INFO is the default level\n// when 'console' is not specified it logs to 'info' or higher\n// when 'file' is not specified it logs to 'info' or higher\nconst infoLogger = loggerApp();\n\n// logs to console and log at 'debug' level and higher\nconst debugLogger = loggerApp({level: 'debug'});\n```\n\n#### Log At `debug` for Console and `warn` for File\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\n\nconst logger = loggerApp({\n  console: 'debug',\n  file: 'warn'\n});\n```\n\n#### Do not log to File\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\n\n// also logs to console at 'info' level\nconst logger = loggerApp({\n  file: false\n});\n```\n\n#### Log to Specific File\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\n\n// also logs to console at 'info' level\nconst logger = loggerApp({\n  file: {\n      path: './path/to/file.log'\n  }\n});\n```\n\n#### Log to Rolling File with Unix Timestamp\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\n\n// also logs to console at 'info' level\nconst logger = loggerApp({\n  file: {\n      timestamp: 'unix'\n  }\n});\n```\n\n#### Log to Rolling File with no timestamp\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\n\n// also logs to console at 'info' level\nconst logger = loggerApp({\n  file: {\n      // specify size but NOT 'frequency' to disable timestamps in filename\n      size: '10M'\n  }\n});\n```\n\n#### Log to additional File for 'error' only\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\nimport { buildDestinationFile } from \"@foxxmd/logging/factory\";\n\nconst errorFileDestination = buildDestinationFile('error', {path: './myLogs/warn.log'});\n\n// also logs to console and file at 'info' level\nconst logger = loggerApp({}, {\n    destinations: [errorFileDestination]\n});\n```\n\n#### Log raw, newline-delimited json logs to additional File\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\nimport { buildDestinationFile } from \"@foxxmd/logging/factory\";\nimport fs from 'node:fs';\n\nconst rawFile = fs.createWriteStream('myRawFile.log');\n\n// also logs to console and file at 'info' level\nconst logger = loggerApp({}, {\n    destinations: [\n      {\n          level: 'debug',\n          stream: rawFile // logs are NOT prettified, only raw data from pino\n      }\n    ]\n});\n```\n\n#### Log prettified data to additional stream\n\nThis could be used to trigger something when a log object with a specific property is found. Or to stream prettified log json to a client over websockets.\n\nTo emit data as an object ([`LogDataPretty`](https://foxxmd.github.io/logging/types/index.LogDataPretty.html)) set `objectMode` and `object` to true.\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\nimport { buildDestinationJsonPrettyStream } from \"@foxxmd/logging/factory\";\nimport { PassThrough } from \"node:stream\";\n\nconst prettyObjectStream = new Passthrough({objectMode: true}); // objectMode MUST be true to get objects from the stream\nconst prettyObjectDestination = buildDestinationJsonPrettyStream('debug', {\n  destination: prettyObjectStream,\n  object: true, // must be set to true to use with objectMode stream\n  colorize: true\n});\n\nconst prettyStringStream = new Passthrough(); // will emit data as a json string\nconst prettyStringDestination = buildDestinationJsonPrettyStream('debug', {\n  destination: prettyStringStream,\n  object: false,\n  colorize: true\n});\n\n// also logs to console and file at 'info' level\nconst logger = loggerApp({}, {\n    destinations: [\n      prettyObjectDestination,\n      prettyStringDestination\n    ]\n});\n\nprettyObjectStream.on('data', (log) =\u003e {\n  // do something with log object (LogDataPretty) \n});\n\nprettyStringStream.on('data', (log) =\u003e {\n  // do something with log string\n});\n```\n\n#### Log to additional Pino Transports\n\nLog to a [Pino Transport](https://getpino.io/#/docs/transports) like [pino-elasticsearch](https://getpino.io/#/docs/transports?id=pino-elasticsearch):\n\n```ts\nimport {loggerApp} from '@foxxmd/logging';\nimport pinoElastic from 'pino-elasticsearch'\n\nconst streamToElastic = pinoElastic({\n  index: 'an-index',\n  node: 'http://localhost:9200',\n  esVersion: 7,\n  flushBytes: 1000\n});\n\n// also logs to console and file at 'info' level\nconst logger = loggerApp({}, {\n    destinations: [\n      {\n          level: 'debug',\n          stream: streamToElastic\n      }\n    ]\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxxmd%2Flogging","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffoxxmd%2Flogging","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffoxxmd%2Flogging/lists"}