{"id":21037591,"url":"https://github.com/faranalytics/streams-logger","last_synced_at":"2025-05-15T14:32:59.760Z","repository":{"id":242907349,"uuid":"810528876","full_name":"faranalytics/streams-logger","owner":"faranalytics","description":"Streams is an intuitive and performant logger for Node.js and TypeScript applications.","archived":false,"fork":false,"pushed_at":"2025-04-07T01:03:44.000Z","size":318,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-09T23:19:44.743Z","etag":null,"topics":["logging","nodejs"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/streams-logger","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/faranalytics.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-06-04T21:51:17.000Z","updated_at":"2025-04-07T01:03:47.000Z","dependencies_parsed_at":"2024-06-18T03:57:18.033Z","dependency_job_id":"50f98e7c-bfac-4048-a5ed-4db31ea285a9","html_url":"https://github.com/faranalytics/streams-logger","commit_stats":null,"previous_names":["faranalytics/streams-logger"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faranalytics%2Fstreams-logger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faranalytics%2Fstreams-logger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faranalytics%2Fstreams-logger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faranalytics%2Fstreams-logger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faranalytics","download_url":"https://codeload.github.com/faranalytics/streams-logger/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254359055,"owners_count":22058036,"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":["logging","nodejs"],"created_at":"2024-11-19T13:27:01.231Z","updated_at":"2025-05-15T14:32:59.745Z","avatar_url":"https://github.com/faranalytics.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# _Streams_ Logger\n\nStreams is an intuitive and performant logger for Node.js _and_ TypeScript applications.\n\n## Introduction\n\n\u003cimg align=\"right\" src=\"./graph.png\"\u003e\n\n_Streams_ is an intuitive logger built on native Node.js streams. You can use the built-in logging components (e.g., the [Logger](#the-logger-class), [Formatter](#the-formatter-class), [Filter](#the-filter-class), [ConsoleHandler](#the-consolehandler-class), [RotatingFileHandler](#the-rotatingfilehandler-class), and [SocketHandler](#the-sockethandler-class)) for [common logging tasks](#usage) or implement your own logging [Node](https://github.com/faranalytics/nodes) to handle a wide range of logging scenarios. _Streams_ offers a graph-like API pattern for building sophisticated logging pipelines.\n\n### Features\n\n- A library of commonly used logging components: [Logger](#the-logger-class), [Formatter](#the-formatter-class), [Filter](#the-filter-class), [ConsoleHandler](#the-consolehandler-class), [RotatingFileHandler](#the-rotatingfilehandler-class), and [SocketHandler](#the-sockethandler-class).\n- A rich selection of [contextual data](#log-context-properties) (e.g., module name, function name, line number, etc.) for augmenting log messages.\n- A type-safe graph-like API pattern for constructing sophisticated [logging graphs](#graph-api-pattern).\n- Consume any native Node.js Readable, Writable, Duplex, or Transform stream and add it to your graph.\n- Error handling and selective detachment of inoperable graph components.\n- Log any type of message you choose - including [objects serialized to JSON](#object-json-logging).\n- Use _Streams_ in your Node.js project, [without type safety](#use-streams-in-a-nodejs-project-without-type-safety-nodejs), or take advantage of the TypeScript type definitions.\n\n## Table of contents\n\n- [Installation](#installation)\n- [Concepts](#concepts)\n- [Usage](#usage)\n- [Examples](#examples)\n- [Formatting](#formatting)\n- [API](#api)\n- [Object (JSON) logging](#object-json-logging)\n- [Using a Socket Handler](#using-a-socket-handler)\n- [Hierarchical logging](#hierarchical-logging)\n- [How-Tos](#how-tos)\n- [Tuning](#tuning)\n- [Backpressure](#backpressure)\n- [Performance](#performance)\n- [Versioning](#versioning)\n- [Test](#test)\n- [Support](#support)\n\n## Installation\n\n```bash\nnpm install streams-logger\n```\n\n## Concepts\n\nLogging is essentially a data transformation task. When a string is logged to the console, for example, it typically undergoes a transformation step where relevant information (e.g., the timestamp, log level, process id, etc.) is added to the log message prior to it being printed. Likewise, when data is written to a file or the console, additional data transformations may take place e.g., serialization and representational transformation. _Streams_ facilitates these data transformation tasks by means of a network of [`Node`](#node) instances that is constructed using a [graph-like API pattern](#graph-api-pattern).\n\n### Node\n\nEach data transformation step in a _Streams_ logging graph is realized through a [`Node`](https://github.com/faranalytics/nodes) implementation. Each `Node` manages and represents a native Node.js stream. A `Node` in a data transformation graph consumes an input, transforms or filters the data in some way, and optionally produces an output. Each component (e.g., Loggers, Formatters, Filters, Handlers, etc.) in a _Streams_ logging graph _is a_ `Node`. Each `Node` _has a_ native Node.js stream that it manages.\n\n### Graph API pattern\n\n_Streams_ uses a graph-like API pattern for constructing a logging graph. Each graph consists of a network of `Node` instances that together comprise a graph logging pipeline. Please see the [Usage](#usage) or [Examples](#examples) for instructions on how to construct a _Streams_ data transformation graph.\n\n## Usage\n\nIn this hypothetical example you will log \"Hello, World!\" to the console and to a file.\n\n### Instructions\n\nImport the Logger, Formatter, ConsoleHandler and RotatingFileHandler, and SyslogLevel enum.\n\n```ts\nimport {\n  Logger,\n  Formatter,\n  ConsoleHandler,\n  RotatingFileHandler,\n  SyslogLevel,\n} from \"streams-logger\";\n```\n\nCreate an instance of a Logger, Formatter, ConsoleHandler and RotatingFileHandler.\n\n- The `Logger` is set to log at level `SyslogLevel.DEBUG`.\n- The `Formatter` constructor is passed a `format` function that will serialize data contained in the `LogContext` to a string containing the ISO time, the log level, the function name, the line number, the column number, and the log message.\n- The `ConsoleHandler` will log the message to `process.stdout`.\n- The `RotatingFileHandler` will log the message to the file `./message.log`.\n\n```ts\nconst logger = new Logger({ level: SyslogLevel.DEBUG });\nconst formatter = new Formatter({\n  format: ({ isotime, message, name, level, func, url, line, col }) =\u003e {\n    return `${isotime}:${level}:${func}:${line}:${col}:${message}\\n`;\n  },\n});\nconst consoleHandler = new ConsoleHandler({ level: SyslogLevel.DEBUG });\nconst rotatingFileHandler = new RotatingFileHandler({\n  path: \"./message.log\",\n  level: SyslogLevel.DEBUG,\n});\n```\n\nConnect the Logger to the Formatter and connect the Formatter to the ConsoleHandler and RotatingFileHandler.\n\n_Streams_ uses a graph-like API pattern in order to construct a network of log Nodes. Each component in a network, in this case the `Logger`, the `Formatter`, and the `ConsoleHandler` and `RotatingFileHandler`, _is a_ [Node](https://github.com/faranalytics/nodes).\n\n```ts\nconst log = logger.connect(\n  formatter.connect(consoleHandler, rotatingFileHandler)\n);\n```\n\nLog \"Hello, World!\" to the console and to the file `./message.log`.\n\n```ts\nfunction sayHello() {\n  log.info(\"Hello, World!\");\n}\n\nsayHello();\n```\n\nOutput\n\n```bash\n# ⮶date-time    function name⮷   column⮷ ⮶message\n2024-06-12T00:10:15.894Z:INFO:sayHello:7:9:Hello, World!\n#                        ⮴level       ⮴line number\n```\n\n## Examples\n\n### _An instance of logging \"Hello, World!\"_ \u003csup\u003e\u003csup\u003e\\\u003c/TypeScript\\\u003e\u003c/sup\u003e\u003c/sup\u003e\n\nPlease see the [Usage](#usage) section above or the [\"Hello, World!\"](https://github.com/faranalytics/streams-logger/tree/main/examples/hello_world) example for a working implementation.\n\n### _Log to a file and the console_ \u003csup\u003e\u003csup\u003e\\\u003c/TypeScript\\\u003e\u003c/sup\u003e\u003c/sup\u003e\n\nPlease see the [_Log to a File and the Console_](https://github.com/faranalytics/streams-logger/tree/main/examples/log_to_a_file_and_the_console) example that demonstrates how to log to a file and the console using different `Formatters`.\n\n### _A network connected streams logging graph_ \u003csup\u003e\u003csup\u003e\\\u003c/TypeScript\\\u003e\u003c/sup\u003e\u003c/sup\u003e\n\nPlease see the [_Network Connected **Streams** Logging Graph_](https://github.com/faranalytics/streams-logger/tree/main/examples/network_connected_logging_graph) example that demonstrates how to connect _Streams_ logging graphs over the network.\n\n### _Use **Streams** in a Node.js project (without type safety)_ \u003csup\u003e\u003csup\u003e\\\u003c/Node.js\\\u003e\u003c/sup\u003e\u003c/sup\u003e\n\nPlease see the [_Use **Streams** in a Node.js Project_](https://github.com/faranalytics/streams-logger/tree/main/examples/use_streams_in_a_node_project) example that demonstrates how to use _Streams_ in a Node.js project _without_ type checks.\n\n## Formatting\n\nYou can format your log message using a `Formatter` Node. The `Logger` constructs a `LogContext` instance on each logged message. The [properties](#log-context-properties) of each `LogContext` contain information about the context of the logged message (e.g., module name, function name, line number, etc.). You can define a serialization function and pass it to the constructor of a `Formatter`. The serialization function can construct a log message from the `LogContext` [properties](#log-context-properties). In the concise [example](#example-formatter) below this is accomplished by using a [template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals).\n\n### Log context properties\n\n_Streams_ provides a rich selection of contextual information with each logging call. This information is provided in a `LogContext` object that is passed as a single argument to the function assigned to the `format` property of the `FormatterOptions` object that is passed to the `Formatter` constructor. Please see the [example](#example-formatter) for instructions on how to incorporate contextual information into your logged message.\n\n| Property   | Description                                                                                                                                                                        | Config Prerequisite      |\n| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |\n| `col`      | The column number of the logging call.                                                                                                                                             | `captureStackTrace=true` |\n| `func`     | The name of the function where the logging call took place.                                                                                                                        | `captureStackTrace=true` |\n| `hostname` | The [hostname](https://nodejs.org/api/os.html#oshostname).                                                                                                                         |                          |\n| `isotime`  | The ISO 8601 [representation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) of the time at which the logging call took place. | `captureISOTime=true`    |\n| `label`    | Optional user specified label.                                                                                                                                                     |                          |\n| `level`    | The `SyslogLevel` of the logging call.                                                                                                                                             |                          |\n| `line`     | The line number of the logging call.                                                                                                                                               | `captureStackTrace=true` |\n| `message`  | The message of the logging call.                                                                                                                                                   |                          |\n| `metadata` | Optional user specified data.                                                                                                                                                      |                          |\n| `name`     | The name of the logger.                                                                                                                                                            |                          |\n| `path`     | The module path.                                                                                                                                                                   | `captureStackTrace=true` |\n| `pathbase` | The module filename.                                                                                                                                                               | `captureStackTrace=true` |\n| `pathdir`  | The directory part of the module path.                                                                                                                                             | `captureStackTrace=true` |\n| `pathext`  | The extension of the module.                                                                                                                                                       | `captureStackTrace=true` |\n| `pathname` | The name of the module.                                                                                                                                                            | `captureStackTrace=true` |\n| `pathroot` | The root of the module.                                                                                                                                                            | `captureStackTrace=true` |\n| `pid`      | The process identifier.                                                                                                                                                            |                          |\n| `stack`    | The complete stack trace.                                                                                                                                                          | `captureStackTrace=true` |\n| `threadid` | The thread identifier.                                                                                                                                                             |                          |\n| `url`      | The URL of the module.                                                                                                                                                             | `captureStackTrace=true` |\n\n\u003e **NB** For high throughput logging applications, you can improve performance by preventing some contextual information from being generated; you can set `Config.captureStackTrace` and `Config.captureISOTime` to `false`. Please see [Tuning](#tuning) for instructions on how to disable contextual information.\n\n### Example formatter\n\nIn the following code excerpt, a formatter is implemented that serializes a `LogContext` to:\n\n1. The time of the logging call in ISO format\n2. The log level\n3. The name of the function where the log event originated\n4. The line number of the log event\n5. The column number of the log event\n6. The log message\n7. A newline\n\nThe `format` function is passed in a `FormatterOptions` object to the constructor of a `Formatter`. The `Logger` is connected to the `Formatter`. The `Formatter` is connected to the `ConsoleHandler`.\n\n```ts\nimport { Logger, Formatter, ConsoleHandler, SyslogLevel } from \"streams-logger\";\n\nconst logger = new Logger({ name: \"main\", level: SyslogLevel.DEBUG });\nconst formatter = new Formatter({\n  format: ({ isotime, message, name, level, func, url, line, col }) =\u003e {\n    return `${isotime}:${level}:${func}:${line}:${col}:${message}\\n`;\n  },\n});\nconst consoleHandler = new ConsoleHandler({ level: SyslogLevel.DEBUG });\n\nconst log = logger.connect(formatter.connect(consoleHandler));\n\nfunction sayHello() {\n  log.info(\"Hello, World!\");\n}\n\nsayHello();\n```\n\nThis is an example of what a logged message will look like using the `Formatter` defined above.\n\n```bash\n# ⮶date-time    function name⮷   column⮷ ⮶message\n2024-06-12T00:10:15.894Z:INFO:sayHello:7:9:Hello, World!\n#                        ⮴level       ⮴line number\n```\n\n## API\n\nThe _Streams_ API provides commonly used logging facilities (i.e., the [Logger](#the-logger-class), [Formatter](#the-formatter-class), [Filter](#the-filter-class), [ConsoleHandler](#the-consolehandler-class), [RotatingFileHandler](#the-rotatingfilehandler-class), and [SocketHandler](#the-sockethandler-class)). However, you can [consume any Node.js stream](#how-to-consume-a-readable-writable-duplex-or-transform-nodejs-stream) and add it to your logging graph.\n\n### The Logger class\n\n#### new streams-logger.Logger\\\u003cMessageT\\\u003e(options, streamOptions)\n\n- `\u003cMessageT\u003e` The type of the logged message. **Default: `\u003cstring\u003e`**\n- options `\u003cLoggerOptions\u003e`\n  - level `\u003cSyslogLevel\u003e` The syslog logger level. **Default: `SyslogLevel.WARN`**\n  - name `\u003cstring\u003e` An optional name for the `Logger`.\n  - parent `\u003cLogger\u003e` An optional parent `Logger`. Set this to `null` in order to disconnect from the root `Logger`.**Default: `streams-logger.root`**\n  - queueSizeLimit `\u003cnumber\u003e` Optionally specify a limit on the number of log messages that may queue while waiting for a stream to drain. See [Backpressure](#backpressure).\n  - captureStackTrace `\u003cboolean\u003e` Optionally specify if stack trace capturing is enabled. This setting will override the default. **Default: `Config.captureStackTrace`**\n  - captureISOTime `\u003cboolean\u003e` Optionally specify if capturing ISO time is enabled. This setting will override the default. **Default: `Config.captureISOTime`**\n- streamOptions `\u003cstream.TransformOptions\u003e` Optional options to be passed to the stream. You can use `TransformOptions` to set a `highWaterMark` on the `Logger`.\n\nUse an instance of a Logger to propagate messages at the specified syslog level.\n\n_public_ **logger.level**\n\n- `\u003cSyslogLevel\u003e`\n\nThe configured log level (e.g., `SyslogLevel.DEBUG`).\n\n_public_ **logger.connect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, unknown\u003e\u003e` Connect to an Array of `Nodes`.\n\nReturns: `\u003cLogger\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, LogContext\u003cMessageT, SyslogLevelT\u003e\u003e`\n\n_public_ **logger.disconnect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, unknown\u003e\u003e` Disconnect from an Array of `Nodes`.\n\nReturns: `\u003cLogger\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, LogContext\u003cMessageT, SyslogLevelT\u003e\u003e`\n\n_public_ **logger.debug(message, label)**\n\n- message `\u003cMessageT\u003e` Write a DEBUG message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.info(message, label)**\n\n- message `\u003cMessageT\u003e` Write a INFO message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.notice(message, label)**\n\n- message `\u003cMessageT\u003e` Write a NOTICE message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.warn(message, label)**\n\n- message `\u003cMessageT\u003e` Write a WARN message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.error(message, label)**\n\n- message `\u003cMessageT\u003e` Write a ERROR message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.crit(message, label)**\n\n- message `\u003cMessageT\u003e` Write a CRIT message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.alert(message, label)**\n\n- message `\u003cMessageT\u003e` Write a ALERT message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.emerg(message, label)**\n\n- message `\u003cMessageT\u003e` Write a EMERG message to the `Logger`.\n- label: `\u003cstring\u003e` An optional label.\n\nReturns: `\u003cvoid\u003e`\n\n_public_ **logger.setLevel(level)**\n\n- level `\u003cSyslogLevel\u003e` A log level.\n\nReturns `\u003cvoid\u003e`\n\nSet the log level. Must be one of `SyslogLevel`.\n\n### The Formatter class\n\n#### new streams-logger.Formatter\\\u003cMessageInT, MessageOutT\\\u003e(options, streamOptions)\n\n- `\u003cMessageInT\u003e` The type of the logged message. This is the type of the `message` property of the `LogContext` that is passed to the `format` function. **Default: `\u003cstring\u003e`**\n- `\u003cMessageOutT\u003e` The type of the output message. This is the return type of the `format` function. **Default: `\u003cstring\u003e`**\n- options\n  - format `(record: LogContext\u003cMessageInT, SyslogLevelT\u003e): Promise\u003cMessageOutT\u003e | MessageOutT` A function that will format and serialize the `LogContext\u003cMessageInT, SyslogLevelT\u003e`. Please see [Formatting](#formatting) for how to implement a format function.\n- streamOptions `\u003cstream.TransformOptions\u003e` Optional options to be passed to the stream. You can use `TransformOptions` to set a `highWaterMark` on the `Formatter`.\n\nUse a `Formatter` in order to specify how your log message will be formatted prior to forwarding it to the Handler(s). An instance of [`LogContext`](#the-LogContext-class) is created that contains information about the environment at the time of the logging call. The `LogContext` is passed as the single argument to `format` function.\n\n_public_ **formatter.connect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageOutT, SyslogLevelT\u003e, unknown\u003e\u003e` Connect to an Array of `Nodes`.\n\nReturns: `\u003cFormatter\u003cLogContext\u003cMessageInT, SyslogLevelT\u003e, LogContext\u003cMessageOutT, SyslogLevelT\u003e\u003e`\n\n_public_ **formatter.disconnect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageOutT, SyslogLevelT\u003e, unknown\u003e\u003e` Disconnect from an Array of `Nodes`.\n\nReturns: `\u003cFormatter\u003cLogContext\u003cMessageInT, SyslogLevelT\u003e, LogContext\u003cMessageOutT, SyslogLevelT\u003e\u003e`\n\n### The Filter class\n\n#### new streams-logger.Filter\\\u003cMessageT\\\u003e(options, streamOptions)\n\n- `\u003cMessageT\u003e` The type of the logged message. **Default: `\u003cstring\u003e`**\n- options\n  - filter `(record: LogContext\u003cMessageT, SyslogLevelT\u003e): Promise\u003cboolean\u003e | boolean` A function that will filter the `LogContext\u003cMessageT, SyslogLevelT\u003e`. Return `true` in order to permit the message through; otherwise, return `false`.\n- streamOptions `\u003cstream.TransformOptions\u003e` Optional options to be passed to the stream. You can use `TransformOptions` to set a `highWaterMark` on the `Filter`.\n\n_public_ **filter.connect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, unknown\u003e\u003e` Connect to an Array of `Nodes`.\n\nReturns: `\u003cFilter\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, LogContext\u003cMessageT, SyslogLevelT\u003e\u003e`\n\n_public_ **filter.disconnect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, unknown\u003e\u003e` Disconnect from an Array of `Nodes`.\n\nReturns: `\u003cFilter\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, LogContext\u003cMessageT, SyslogLevelT\u003e\u003e`\n\n### The ConsoleHandler class\n\n#### new streams-logger.ConsoleHandler\\\u003cMessageT\\\u003e(options, streamOptions)\n\n- `\u003cMessageT\u003e` The type of the logged message. **Default: `\u003cstring\u003e`**\n- options `\u003cConsoleHandlerOptions\u003e`\n  - level `\u003cSyslogLevel\u003e` An optional log level. **Default: `SyslogLevel.WARN`**\n- streamOptions `\u003cstream.TransformOptions\u003e` Optional options to be passed to the stream. You can use `TransformOptions` to set a `highWaterMark` on the `ConsoleHandler`.\n\nUse a `ConsoleHandler` in order to stream your messages to the console.\n\n_public_ **consoleHandler.setLevel(level)**\n\n- level `\u003cSyslogLevel\u003e` A log level.\n\nReturns `\u003cvoid\u003e`\n\nSet the log level. Must be one of `SyslogLevel`.\n\n### The RotatingFileHandler class\n\n#### new streams-logger.RotatingFileHandler\\\u003cMessageT\\\u003e(options, streamOptions)\n\n- `\u003cMessageT\u003e` The type of the logged message. **Default: `\u003cstring\u003e`**\n- options `\u003cRotatingFileHandlerOptions\u003e`\n  - path `\u003cstring\u003e` The path of the log file.\n  - rotationLimit `\u003cnumber\u003e` An optional number of log rotations. **Default: `0`**\n  - maxSize `\u003cnumber\u003e` The size of the log file in bytes that will initiate a rotation. **Default: `1e6`**\n  - encoding `\u003cBufferEncoding\u003e` An optional encoding. **Default: `utf-8`**\n  - mode `\u003cnumber\u003e` An optional mode. **Deafult: `0o666`**\n  - level `\u003cSyslogLevel\u003e` An optional log level. **Default: `SyslogLevel.WARN`**\n  - flags `\u003cstring\u003e` An optional file system flag. **Default: `a`**\n- streamOptions `\u003cstream.WritableOptions\u003e` Optional options to be passed to the stream. You can use `WritableOptions` to set a `highWaterMark` on the `RotatingFileHandler`.\n\nUse a `RotatingFileHandler` in order to write your log messages to a file.\n\n\u003e **NB** For improved performance, the `RotatingFileHandler` maintains its own accounting of the log file size for purposes of file rotation; hence, it's important that out-of-band writes are not permitted on the same log file while it is operating on it.\n\n_public_ **rotatingFileHandler.setLevel(level)**\n\n- level `\u003cSyslogLevel\u003e` A log level.\n\nReturns `\u003cvoid\u003e`\n\nSet the log level. Must be one of `SyslogLevel`.\n\n### The SocketHandler class\n\n#### new streams-logger.SocketHandler\\\u003cMessageT\\\u003e(options, streamOptions)\n\n- `\u003cMessageT\u003e` The type of the logged message. **Default: `\u003cstring\u003e`**\n- options `\u003cSocketHandlerOptions\u003e`\n  - socket `\u003cSocket\u003e` A `net.Socket` that will serve as a communication channel between this `SocketHandler` and the remote `SocketHandler`.\n  - reviver `\u003c(this: unknown, key: string, value: unknown) =\u003e unknown\u003e` An optional reviver for `JSON.parse`.\n  - replacer `\u003c(this: unknown, key: string, value: unknown) =\u003e unknown\u003e` An optional replacer for `JSON.stringify`.\n  - space `\u003cstring | number\u003e` An optional space specification for `JSON.stringify`.\n- streamOptions `\u003cstream.DuplexOptions\u003e` Optional options to be passed to the stream. You can use `DuplexOptions` to set a `highWaterMark` on the `SocketHandler`.\n\nUse a `SocketHandler` in order to connect _Streams_ graphs over the network. Please see the [_A Network Connected **Streams** Logging Graph_](#a-network-connected-streams-logging-graph-typescript) example for instructions on how to use a `SocketHandler` in order to connect _Streams_ logging graphs over the network.\n\n_public_ **socketHandler.connect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, unknown\u003e\u003e` Connect to an Array of `Nodes`.\n\nReturns: `\u003cSocketHandler\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, LogContext\u003cMessageT, SyslogLevelT\u003e\u003e`\n\n_public_ **socketHandler.disconnect(...nodes)**\n\n- nodes `\u003cArray\u003cNode\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, unknown\u003e\u003e` Disconnect from an Array of `Nodes`.\n\nReturns: `\u003cSocketHandler\u003cLogContext\u003cMessageT, SyslogLevelT\u003e, LogContext\u003cMessageT, SyslogLevelT\u003e\u003e`\n\n_public_ **socketHandler.setLevel(level)**\n\n- level `\u003cSyslogLevel\u003e` A log level.\n\nReturns `\u003cvoid\u003e`\n\nSet the log level. Must be one of `SyslogLevel`.\n\n### The LogContext class\n\n#### new streams-logger.LogContext\\\u003cMessageT, LevelT\\\u003e(options)\n\n- `\u003cMessageT\u003e` The type of the logged message. **Default: `\u003cstring\u003e`**\n- `\u003cLevelT\u003e` The type of the Level enum. **Default: `\u003cSyslogLevelT\u003e`**\n- options `\u003cLoggerOptions\u003e`\n  - message `\u003cMessageT\u003e` The logged message.\n  - name `\u003cstring\u003e` The name of the `Logger`.\n  - level `\u003cKeysUppercase\u003cLevelT\u003e` An uppercase string representing the log level.\n  - stack `\u003cstring\u003e` An optional stack trace.\n\nA `LogContext` is instantiated each time a message is logged at (or below) the level set on the `Logger`. It contains information about the process and environment at the time of the logging call. All _Streams_ Nodes take a `LogContext` as an input and emit a `LogContext` as an output.\n\nThe `LogContext` is passed as the single argument to the [format function](#formatting) of the `Formatter`; information about the environment can be extracted from the `LogContext` in order to format the logged message. The following properties will be available to the `format` function depending on the setting of `Config.captureStackTrace` and `Config.captureISOTime`. Please see the [Log Context Data](#log-context-data) table for details.\n\n_public_ **logContext.col**\n\n- `\u003cstring\u003e`\n  The column of the logging call. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.func**\n\n- `\u003cstring\u003e`\n  The name of the function where the logging call took place. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.hostname**\n\n- `\u003cstring\u003e`\n  The hostname.\n\n_public_ **logContext.isotime**\n\n- `\u003cstring\u003e`\n  The date and time in ISO format at the time of the logging call. Available if `Config.captureISOTime` is set to `true`.\n\n_public_ **logContext.level**\n\n- `\u003cDEBUG | INFO | NOTICE | WARN | ERROR | CRIT | ALERT | EMERG\u003e`\n  An uppercase string representation of the level.\n\n_public_ **logContext.line**\n\n- `\u003cstring\u003e`\n  The line number of the logging call. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.message**\n\n- `\u003cstring\u003e`\n  The logged message.\n\n_public_ **logContext.metadata**\n\n- `\u003cunknown\u003e`\n  Optional user specified data.\n\n_public_ **logContext.name**\n\n- `\u003cstring\u003e`\n  The name of the `Logger`.\n\n_public_ **logContext.path**\n\n- `\u003cstring\u003e`\n  The complete path of the module. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.pathbase**\n\n- `\u003cstring\u003e`\n  The module filename. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.pathext**\n\n- `\u003cstring\u003e`\n  The extension of the module. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.pathdir**\n\n- `\u003cstring\u003e`\n  The directory part of the module path. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.pathname**\n\n- `\u003cstring\u003e`\n  The name of the module. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.pathroot**\n\n- `\u003cstring\u003e`\n  The root of the path. Available if `Config.captureStackTrace` is set to `true`.\n\n_public_ **logContext.pid**\n\n- `\u003cstring\u003e`\n  The process identifier.\n\n_public_ **logContext.threadid**\n\n- `\u003cstring\u003e`\n  The thread identifier.\n\n_public_ **logContext.parseStackTrace(depth)**\n\n- depth `\u003cnumber\u003e` An optional depth i.e., the number of newlines to skip.\n\nReturns `\u003cvoid\u003e`\n\nIf the `stack` property has been set, parse the stack trace.\n\n### The _Streams_ Config settings object\n\nThe `Config` object is used to set default settings. It can be used for performance [tuning](#tuning).\n\n**Config.errorHandler** `\u003c(err: Error, ...params: Array\u003cunknown\u003e) =\u003e void\u003e` Set an error handler. **Default: `console.error`**\n\n**Config.captureISOTime** `\u003cboolean\u003e` Set this to `false` in order to disable capturing ISO time on each logging call. **Default: `true`**\n\n**Config.captureStackTrace** `\u003cboolean\u003e` Set this to `false` in order to disable stack trace capture on each logging call. **Default: `true`**\n\n**Config.highWaterMark** `\u003cnumber\u003e` Set the `highWaterMark` for streams in Buffer mode. **Default: `node:stream.getDefaultHighWaterMark(false)`**\n\n**Config.highWaterMarkObjectMode** `\u003cnumber\u003e` Set the `highWaterMark` for streams in objectMode. **Default: `node:stream.getDefaultHighWaterMark(true)`**\n\n**Config.getDuplexOptions(writableObjectMode, readableObjectMode)**\n\n- writableObjectMode `\u003cboolean\u003e` `true` for ObjectMode; `false` otherwise.\n- readableObjectMode `\u003cboolean\u003e` `true` for ObjectMode; `false` otherwise.\n\nReturns: `\u003cstream.DuplexOptions\u003e`\n\nUse `Config.getDuplexOptions` when implementing a [custom _Streams_ data transformation Node](#how-to-implement-a-custom-streams-data-transformation-node).\n\n**Config.getReadableOptions(readableObjectMode)**\n\n- readableObjectMode `\u003cboolean\u003e` `true` for ObjectMode; `false` otherwise.\n\nReturns: `\u003cstream.ReadableOptions\u003e`\n\nUse `Config.getReadableOptions` when implementing a [custom _Streams_ data transformation Node](#how-to-implement-a-custom-streams-data-transformation-node).\n\n**Config.getWritableOptions(writableObjectMode)**\n\n- writableObjectMode `\u003cboolean\u003e` `true` for ObjectMode; `false` otherwise.\n\nReturns: `\u003cstream.WritableOptions\u003e`\n\nUse `Config.getWritableOptions` when implementing a [custom _Streams_ data transformation Node](#how-to-implement-a-custom-streams-data-transformation-node).\n\n### The SyslogLevel enum\n\n#### streams-logger.SyslogLevel\\[Level\\]\n\n- Level\n  - EMERG = 0\n  - ALERT = 1\n  - CRIT = 2\n  - ERROR = 3\n  - WARN = 4\n  - NOTICE = 5\n  - INFO = 6\n  - DEBUG = 7\n\nUse `SyslogLevel` to set the level in the options passed to `Logger`, `Filter`, and Handler constructors.\n\n## Object (JSON) logging\n\n_Streams_ logging facilities (e.g., Logger, Formatter, etc.) default to logging `string` messages; however, you can log any type of message you want by specifying your message type in the type parameter of the constructor. In the following example, a permissive interface is created named `Message`. The `Message` type is specified in the type parameter of the constructor of each `Node` (i.e., the Logger, Formatter, and ConsoleHandler). The `Formatter` is configured to input a `Message` and output a `string`; `Message` objects are serialized using `JSON.stringify`.\n\n```ts\nimport { Logger, Formatter, ConsoleHandler, SyslogLevel } from \"streams-logger\";\n\ninterface Message {\n  [key: string]: string | number;\n}\n\nconst logger = new Logger\u003cMessage\u003e({ level: SyslogLevel.DEBUG });\nconst formatter = new Formatter\u003cMessage, string\u003e({\n  format: ({ isotime, message, level, func, line, col }) =\u003e {\n    return `${isotime}:${level}:${func}:${line}:${col}:${JSON.stringify(\n      message\n    )}\\n`;\n  },\n});\nconst consoleHandler = new ConsoleHandler\u003cstring\u003e({ level: SyslogLevel.DEBUG });\n\nconst log = logger.connect(formatter.connect(consoleHandler));\n\n(function sayHello() {\n  log.warn({ greeting: \"Hello, World!\", prime_number: 57 });\n})();\n```\n\nOutput\n\n```bash\n# ⮶date-time    function name⮷ column⮷    ⮶message\n2024-07-06T03:19:28.767Z:WARN:sayHello:9:9:{\"greeting\":\"Hello, World!\",\"prime_number\":57}\n#                        ⮴level          ⮴line number\n```\n\n## Using a Socket Handler\n\n_Streams_ uses Node.js streams for message propagation. Node.js represents sockets as streams; hence, sockets are a natural extension of a _Streams_ logging graph. For example, you may choose to use a `ConsoleHandler` locally and log to a `RotatingFileHandler` on a remote server. Please see the [_A Network Connected **Streams** Logging Graph_](#a-network-connected-streams-logging-graph-typescript) example for a working implementation.\n\n### Security\n\nThe `SocketHandler` options take a socket instance as an argument. The `net.Server` that produces this socket may be configured however you choose. You can encrypt the data sent over TCP connections and authenticate clients by configuring your `net.Server` accordingly.\n\n#### Configure your server to use TLS encryption.\n\nTLS Encryption may be implemented using native Node.js [TLS Encryption](https://nodejs.org/docs/latest-v20.x/api/tls.html).\n\n#### Configure your client to use TLS client certificate authentication.\n\nTLS Client Certificate Authentication may be implemented using native Node.js [TLS Client Authentication](https://nodejs.org/docs/latest-v20.x/api/tls.html).\n\n## Hierarchical logging\n\n_Streams_ supports hierarchical logging. By default every `Logger` instance is connected to the root `Logger` (`streams-logger.root`). However, you may optionally specify an antecedent other than `root` by assigning an instance of `Logger` to the `parent` property in the `LoggerOptions`. The antecedent of the root `Logger` is `null`.\n\nYou may capture logging events from other modules (_and your own_) by connecting a data handler `Node` (e.g., a `ConsoleHandler`) to the `streams-logger.root` `Logger`. E.g.,\n\n```ts\nimport { Formatter, ConsoleHandler, SyslogLevel, root } from \"streams-logger\";\n\nconst formatter = new Formatter({\n  format: ({ isotime, message, name, level, func, url, line, col }) =\u003e {\n    return `${isotime}:${level}:${func}:${line}:${col}:${message}\\n`;\n  },\n});\nconst consoleHandler = new ConsoleHandler({ level: SyslogLevel.DEBUG });\n\nroot.connect(formatter.connect(consoleHandler));\n```\n\n## How-Tos\n\n### How to implement a custom _Streams_ data transformation node\n\n_Streams_ is built on the type-safe [Nodes](https://github.com/faranalytics/nodes) graph API framework. This means that any Nodes `Node` may be incorporated into your logging graph provided that it meets the contextual type requirements. In order to implement a _Streams_ data transformation `Node`, subclass the `Node` class, and provide the appropriate _Streams_ defaults to the stream constructor.\n\nFor example, the somewhat contrived `LogContextToBuffer` implementation transforms the `message` contained in a `LogContext` to a `Buffer`; the graph pipeline streams the message to `process.stdout`.\n\n\u003e **NB** In this example, `writableObjectMode` is set to `true` and `readableObjectMode` is set to `false`; hence, the Node.js stream implementation will handle the input as a `object` and the output as an `Buffer`. It's important that `writableObjectMode` and `readableObjectMode` accurately reflect the input and output types of your Node.\n\n#### Implement the `index.ts` module.\n\n```ts\nimport * as stream from \"node:stream\";\nimport { Logger, Node, Config, LogContext, SyslogLevelT } from \"streams-logger\";\n\nexport class LogContextToBuffer extends Node\u003c\n  LogContext\u003cstring, SyslogLevelT\u003e,\n  Buffer\n\u003e {\n  public encoding: NodeJS.BufferEncoding = \"utf-8\";\n\n  constructor(streamOptions?: stream.TransformOptions) {\n    super(\n      new stream.Transform({\n        ...Config.getDuplexOptions(true, false),\n        ...streamOptions,\n        ...{\n          writableObjectMode: true,\n          readableObjectMode: false,\n          transform: (\n            chunk: LogContext\u003cstring, SyslogLevelT\u003e,\n            encoding: BufferEncoding,\n            callback: stream.TransformCallback\n          ) =\u003e {\n            callback(null, Buffer.from(chunk.message, this.encoding));\n          },\n        },\n      })\n    );\n  }\n}\n\nconst log = new Logger({ name: \"main\" });\nconst logContextToBuffer = new LogContextToBuffer();\nconst console = new Node\u003cBuffer, never\u003e(process.stdout);\n\nlog.connect(logContextToBuffer.connect(console));\n\nlog.warn(\"Hello, World!\");\n```\n\n##### Output\n\n```bash\nHello, World!\n```\n\n_Streams_ provides a few examples of [handlers](https://github.com/faranalytics/streams-logger/tree/main/src/handlers), which you can use for modeling your data transformation `Node`.\n\n### How to consume a Readable, Writable, Duplex, or Transform Node.js stream\n\nYou can incorporate any Readable, Writable, Duplex, or Transform stream into your logging graph, provided that it meets the contextual type requirements, by passing the stream to the `Node` constructor. In this hypothetical example a type-safe `Node` is constructed from a `net.Socket`. The type variables are specified as `\u003cBuffer, Buffer\u003e`; the writable side of the stream consumes a `Buffer` and the readable side of the stream produces a `Buffer`.\n\n```ts\nimport * as net from \"node:net\";\nimport { once } from \"node:events\";\nimport { Node } from \"streams-logger\";\n\nnet.createServer((socket: net.Socket) =\u003e socket.pipe(socket)).listen(3000);\nconst socket = net.createConnection({ port: 3000 });\nawait once(socket, \"connect\");\nconst socketHandler = new Node\u003cBuffer, Buffer\u003e(socket); // Connect this `Node` to any `Node` that produces a Buffer.\n```\n\n## Tuning\n\n**Depending on your requirements, the defaults may be fine.** However, for high throughput logging applications you may choose to adjust the `highWaterMark`, disconnect your `Logger` from the root `Logger`, and/or disable stack trace capturing.\n\n### Tune the highWaterMark\n\n_Streams_ `Node` implementations use the native Node.js stream API for message propagation. You have the option of tuning the Node.js stream `highWaterMark` to your specific needs - keeping in mind memory constraints. You can set a `highWaterMark` using [`Config.highWaterMark`](#the-streams-config-settings-object) and [`Config.highWaterMarkObjectMode`](#the-streams-config-settings-object) that will apply to Nodes in the _Streams_ library. Alternatively, the `highWaterMark` can be set in the constructor of each `Node`; please see the [API](#api) for instructions on how to do this.\n\nIn this example, the `highWaterMark` of ObjectMode streams and Buffer mode streams is artificially set to `1e6` objects and `1e6` bytes.\n\n```ts\nimport * as streams from \"streams-logger\";\n\nstreams.Config.highWaterMark = 1e6;\nstreams.Config.highWaterMarkObjectMode = 1e6;\n```\n\n\u003e Please see the [API](#api) for more information on [`Config`](#the-streams-config-settings-object) object settings.\n\n### Disable stack trace capture\n\nAnother optional setting that you can take advantage of is to turn off stack trace capture. Stack trace capture can be disabled globally using the _Streams_ configuration settings object i.e., [`Config.captureStackTrace`](#the-streams-config-settings-object). Alternatively, you may disable stack trace capturing in a specific `Logger` by setting the `captureStackTrace` property of the `LoggerOptions` to `false`.\n\nTurning off stack trace capture will disable some of the information (e.g., function name and line number) that is normally contained in the `LogContext` object that is passed to the `format` function of a `Formatter`.\n\nYou can turn off stack trace capturing for all `Logger` instances.\n\n```ts\nimport * as streams from \"streams-logger\";\n\nstreams.Config.captureStackTrace = false;\n```\n\nAlternatively, you can instantiate a `Logger` with stack trace capturing disabled.\n\n```ts\nconst logger = new Logger({ captureStackTrace: false });\n```\n\n### Disconnect from root\n\nYou can optionally disconnect your `Logger` from the root `Logger` or a specified antecedent. This will prevent message propagation to the root logger, which will provide cost savings and isolation. You can either set the `parent` parameter to `null` in the constructor of the `Logger` or explicitely disconnect from the root `Logger` using the `disconnect` method of the `Logger` instance. In this example the `Logger` instance is disconnected from the _Streams_ root logger after instantiation.\n\n```ts\nimport * as streams from 'streams-logger';\n...\nconst log = logger.connect(\n    formatter.connect(\n        consoleHandler\n    )\n);\n\nlog.disconnect(streams.root);\n```\n\n### Putting it all together\n\nIf you have a high throughput logging application, the following settings should get you to where you want to be while keeping Node.js stream buffers in check.\n\n```ts\nimport * as streams from 'streams-logger';\n\nstreams.Config.highWaterMark = 1e5;\nstreams.Config.highWaterMarkObjectMode = 1e5;\n\nconst logger = new Logger({ parent: null, captureStackTrace: false });\n\n... // Create an instance of a `Formatter` and `ConsoleHandler`.\n\nconst log = logger.connect(\n    formatter.connect(\n        consoleHandler\n    )\n);\n```\n\nHowever, for typical error logging applications or debugging scenarios the defaults should work fine.\n\n## Backpressure\n\n_Streams_ respects backpressure by queueing messages while the stream is draining. You can set a limit on how large the message queue may grow by specifying a `queueSizeLimit` in the Logger constructor options. If a `queueSizeLimit` is specified and if it is exceeded, the `Logger` will throw a `QueueSizeLimitExceededError`.\n\n**For typical logging applications setting a `queueSizeLimit` isn't necessary.** However, if an uncooperative stream peer reads data at a rate that is slower than the rate that data is written to the stream, data may buffer until memory is exhausted. By setting a `queueSizeLimit` you can effectively respond to subversive stream peers and disconnect offending Nodes in your graph.\n\nIf you have a _cooperating_ stream that is backpressuring, you can either set a default `highWaterMark` appropriate to your application or increase the `highWaterMark` on the specific stream in order to mitigate drain events.\n\n## Performance\n\n_Streams_ is a highly customizable logger that performs well on a wide range of logging tasks. It is a good choice for both error logging and high throughput logging. It strictly adheres to the Node.js public API contract and common conventions. This approach comes with trade-offs; however, it ensures stability and portability while still delivering a performant logging experience.\n\n\u003e Please see [Tuning](#tuning) for how to configure the logging graph for high throughput logging applications.\n\n## Versioning\n\nThe _Streams_ package adheres to semantic versioning. Breaking changes to the public API will result in a turn of the major. Minor and patch changes will always be backward compatible.\n\nExcerpted from [Semantic Versioning 2.0.0](https://semver.org/):\n\n\u003e Given a version number MAJOR.MINOR.PATCH, increment the:\n\u003e\n\u003e 1. MAJOR version when you make incompatible API changes\n\u003e 2. MINOR version when you add functionality in a backward compatible manner\n\u003e 3. PATCH version when you make backward compatible bug fixes\n\u003e\n\u003e Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.\n\n## Test\n\n### How to run the test\n\n#### Clone the repository.\n\n```bash\ngit clone https://github.com/faranalytics/streams-logger.git\n```\n\n#### Change directory into the root of the repository.\n\n```bash\ncd streams-logger\n```\n\n#### Install dependencies.\n\n```bash\nnpm install \u0026\u0026 npm update\n```\n\n#### Run the tests.\n\n```bash\nnpm test verbose=false\n```\n\n## Support\n\nIf you have a feature request or run into any issues, feel free to submit an [issue](https://github.com/faranalytics/streams-logger/issues) or start a [discussion](https://github.com/faranalytics/streams-logger/discussions). You’re also welcome to reach out directly to one of the authors.\n\n- [Adam Patterson](https://github.com/adamjpatterson)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaranalytics%2Fstreams-logger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaranalytics%2Fstreams-logger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaranalytics%2Fstreams-logger/lists"}