diff --git a/README.md b/README.md index 5f61cfeb..fba81044 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![GitHub stars](https://img.shields.io/github/stars/fullstack-build/tslog.svg?style=social&label=Star)](https://github.com/fullstack-build/tslog) -> Powerful, fast and expressive logging for Node.js +> Powerful, fast and expressive logging for Node.js ![tslog pretty output](https://raw.githubusercontent.com/fullstack-build/tslog/master/docs/assets/tslog_pretty_output.png "tslog pretty output") @@ -39,14 +39,14 @@ const log: Logger = new Logger(); log.silly("I am a silly log."); ``` -### Install +### Install ```bash npm install tslog ``` **Enable TypeScript source map support:** -This feature enables `tslog` to reference a correct line number in your TypeScript source code. +This feature enables `tslog` to reference a correct line number in your TypeScript source code. ```json5 // tsconfig.json @@ -79,12 +79,12 @@ log.fatal(new Error("I am a pretty Error with a stacktrace.")); * **Log level:** `silly`, `trace`, `debug`, `info`, `warn`, `error`, `fatal` (different colors) * **Output to std:** Structured/_pretty_ output (easy parsable `tab` delimiters), `JSON` or suppressed * **Attachable transports:** Send logs to an external log aggregation services, file system, database, or email/slack/sms/you name it... -* **StdOut or StdErr depends on log level:** **_stdout_** for `silly`, `trace`, `debug`, `info` and **_stderr_** for `warn`, `error`, `fatal` +* **StdOut or StdErr depends on log level:** **_stdout_** for `silly`, `trace`, `debug`, `info` and **_stderr_** for `warn`, `error`, `fatal` * **Minimum log level per output:** `minLevel` level can be set individually per transport * **Fully typed:** Written in TypeScript, fully typed, API checked with _api-extractor_, _TSDoc_ documented * **Source maps lookup:** Shows exact position also in TypeScript code (compile-to-JS), one click to IDE position -* **Stack trace:** Callsites through native _V8 stack trace API_, excludes internal entries -* **Pretty Error:** Errors and stack traces printed in a structured way and fully accessible through _JSON_ (e.g. external Log services) +* **Stack trace:** Callsites through native _V8 stack trace API_, excludes internal entries +* **Pretty Error:** Errors and stack traces printed in a structured way and fully accessible through _JSON_ (e.g. external Log services) * **Code frame:** `tslog` captures and displays the source code that lead to an error, making it easier to debug * **Object/JSON highlighting:** Nicely prints out an object using native Node.js `utils.inspect` method * **Instance Name:** Logs capture instance name (default host name) making it easy to distinguish logs coming from different instances (e.g. serverless) @@ -177,7 +177,7 @@ interface ILogObject { } ``` -There are three ways to access this object: +There are three ways to access this object: ##### Returned by each log method @@ -196,7 +196,7 @@ console.log(JSON.stringify(logWithTrace, null, 2)); ```typescript new Logger({ type: "json" }); ``` -Resulting in the following output: +Resulting in the following output: ![tslog log level json](https://raw.githubusercontent.com/fullstack-build/tslog/master/docs/assets/tslog_log_level_json.png) @@ -206,8 +206,8 @@ Resulting in the following output: #### Log level -`tslog` is highly customizable, however, it follows _convention over configuration_ when it comes to **log levels**. -Internally a log level is represented by a numeric ID. +`tslog` is highly customizable, however, it follows _convention over configuration_ when it comes to **log levels**. +Internally a log level is represented by a numeric ID. Available log levels are:
`0: silly`, `1: trace`, `2: debug`, `3: info`, `4: warn`, `5: error`, `6: fatal` @@ -215,9 +215,9 @@ Available log levels are:
Per default log level 0 - 3 are written to `stdout` and 4 - 6 are written to `stderr`. Each log level is printed in a different color, that is customizable through the settings object. -> **Hint:** Log level `trace` behaves a bit differently compared to all the other log levels. -> While it is possible to activate a stack trace for every log level, it is already activated for `trace` by default. -> That means every `trace` log will also automatically capture and print its entire stack trace. +> **Hint:** Log level `trace` behaves a bit differently compared to all the other log levels. +> While it is possible to activate a stack trace for every log level, it is already activated for `trace` by default. +> That means every `trace` log will also automatically capture and print its entire stack trace. ```typescript import { Logger } from "tslog"; @@ -232,17 +232,17 @@ log.error("I am an error log."); log.fatal(new Error("I am a pretty Error with a stacktrace.")); ``` -Structured (aka. _pretty_) log level output would look like this: +Structured (aka. _pretty_) log level output would look like this: ![tslog log level structured](https://raw.githubusercontent.com/fullstack-build/tslog/master/docs/assets/tslog_log_level_pretty.png "tslog log level structured") > **Hint:** Each logging method has a return type, which is a _JSON_ representation of the log message (`ILogObject`). -> You can use this object to access its stack trace etc. -> More details +> You can use this object to access its stack trace etc. +> More details #### Child Logger -Each `tslog` Logger instance can create child loggers and bequeath its settings to a child. +Each `tslog` Logger instance can create child loggers and bequeath its settings to a child. It is also possible to overwrite every setting when creating a child.
Child loggers are a powerful feature when building a modular application and due to its inheritance make it easy to configure the entire application. @@ -258,44 +258,58 @@ const grandchildLogger: Logger = childLogger.getChildLogger({ name: "GrandChild ``` +#### Creating logger without source map support + +By default, `Logger` creates instance with stack trace's source mapping support. For some cases, it may not be needed. `LoggerWithoutCallSite` returns same interface as `Logger` does and only disabling call site wrapping for source map. + +```typescript +import { Logger, LoggerWithoutCallSite } from 'tslog'; + +const logger = new Logger(...); +const loggerWithoutCallSite = new LoggerWithoutCallSite(...); + +``` + +Since `tslog` supports [tree-shaking](https://webpack.js.org/guides/tree-shaking/) via esm import syntax, importing `LoggerWithoutCallSite` without `Logger` will reduce overall bundle size. + #### Settings - -As `tslog` follows _convention over configuration_, it already comes with reasonable default settings. + +As `tslog` follows _convention over configuration_, it already comes with reasonable default settings. Therefor all settings are optional. Nevertheless, they can be flexibly adapted to your own needs. All possible settings are defined in the `ISettingsParam` interface and modern IDEs will provide auto-completion accordingly. - + **You can use `setSettings()` to adjust settings at runtime.** -> **Hint:** When changing settings at runtime this alternation would also propagate to every child loggers, as long as it has not been overwritten down the hierarchy. +> **Hint:** When changing settings at runtime this alternation would also propagate to every child loggers, as long as it has not been overwritten down the hierarchy. -##### `type` +##### `type` ```default: "pretty"``` -Possible values: `"json" | "pretty" | "hidden"` +Possible values: `"json" | "pretty" | "hidden"` You can either `pretty` print logs, print them as `json` or hide them all together with `hidden` (e.g. when using custom transports).
Having `json` as an output format is particularly useful, if you want to forward your logs directly from your `std` to another log service. -Instead of parsing a _pretty_ output, most log services prefer a _JSON_ representation. +Instead of parsing a _pretty_ output, most log services prefer a _JSON_ representation. > **Hint:** Printing in `json` gives you direct access to all the available information, like _stack trace_ and _code frame_ and so on. ```typescript new Logger({ type: "json" }); ``` -_Output:_ +_Output:_ ![tslog log level json](https://raw.githubusercontent.com/fullstack-build/tslog/master/docs/assets/tslog_log_level_json.png) > **Hint:** Each _JSON_ log is printed in one line, making it easily parsable by external services. ##### `instanceName` ```default: os.hostname``` _(hidden by default)_ -You can provide each logger with the name of the instance, making it easy to distinguish logs from different machines. -This approach works well in the serverless environment as well, allowing you to filter all logs coming from a certain instance. +You can provide each logger with the name of the instance, making it easy to distinguish logs from different machines. +This approach works well in the serverless environment as well, allowing you to filter all logs coming from a certain instance. -Per default `instanceName` is pre-filled with the `hostname` of your environment, which can be overwritten. -However, this value is hidden by default in order to keep the log clean and tidy. -You can change this behavior by setting `displayInstanceName` to `true`. +Per default `instanceName` is pre-filled with the `hostname` of your environment, which can be overwritten. +However, this value is hidden by default in order to keep the log clean and tidy. +You can change this behavior by setting `displayInstanceName` to `true`. ```typescript @@ -304,15 +318,15 @@ const logger: Logger = new Logger({ displayInstanceName: true }); const logger: Logger = new Logger({ displayInstanceName: true, instanceName: "ABC" }); // Would print out ABC as the name of this instance - + ``` ##### `name` ```default: undefined``` -Each logger has an optional name, that is hidden by default. You can change this behavior by setting `displayLoggerName` to `true`. -This setting is particularly interesting when working in a `monorepo`, -giving you the possibility to provide each module/package with its own logger and being able to distinguish logs coming from different parts of your application. +Each logger has an optional name, that is hidden by default. You can change this behavior by setting `displayLoggerName` to `true`. +This setting is particularly interesting when working in a `monorepo`, +giving you the possibility to provide each module/package with its own logger and being able to distinguish logs coming from different parts of your application. ```typescript new Logger({ name: "myLogger" }); @@ -331,24 +345,24 @@ new Logger({ setCallerAsLoggerName: true }); ##### `minLevel` ```default: "silly"``` -Minimum log level to be captured by this logger. +Minimum log level to be captured by this logger. Possible values are: `silly`, `trace`, `debug`, `info`, `warn`, `error`, `fatal` ##### `requestId` ```default: undefined``` -**❗ Keep track of all subsequent calls and promises originated from a single request (e.g. HTTP).** +**❗ Keep track of all subsequent calls and promises originated from a single request (e.g. HTTP).** In a real world application a call to an API would lead to many logs produced across the entire application. -When debugging it can get quite handy to be able to group all logs based by a unique identifier `requestId`. +When debugging it can get quite handy to be able to group all logs based by a unique identifier `requestId`. A `requestId` can either be a `string` or a function.
-A string is suitable when you create a child logger for each request, while a function is helpful, when you need to reuse the same logger and need to obtain a `requistId` dynamically. +A string is suitable when you create a child logger for each request, while a function is helpful, when you need to reuse the same logger and need to obtain a `requistId` dynamically. **With Node.js 13.10, we got a new feature called AsyncLocalStorage.**
It has also been backported to Node.js v12.17.0 and of course it works with Node.js >= 14.
However it is still marked as *experimental*.
-Here is a blog post by Andrey Pechkurov describing ``AsyncLocalStorage`` and performing a small performance comparison. +Here is a blog post by Andrey Pechkurov describing ``AsyncLocalStorage`` and performing a small performance comparison. > **Hint**: If you prefer to use a more proven (yet slower) approach, you may want to check out `cls-hooked`. @@ -356,12 +370,12 @@ Even though `tslog` is generic enough and works with any of these solutions our `tslog` also works with any API framework (like `Express`, `Koa`, `Hapi` and so on), but we are going to use `Koa` in this example.
Based on this example it should be rather easy to create an `Express` or another middleware. -Some provides (e.g. `Heroku`) already set a `X-Request-ID` header, which we are going to use or fallback to a short ID generated by `nanoid`. +Some provides (e.g. `Heroku`) already set a `X-Request-ID` header, which we are going to use or fallback to a short ID generated by `nanoid`. **In this example every subsequent logger is a child logger of the main logger and thus inherits all of its settings making `requestId` available throughout the entire application without any further ado.** _index.ts:_ -```typescript +```typescript import * as Koa from 'koa'; import { AsyncLocalStorage } from "async_hooks"; import { customAlphabet } from "nanoid"; @@ -402,8 +416,8 @@ childLogger.info("Log containing requestId"); // <-- will contain a requestId ##### `exposeStack` ```default: false``` - -Usually, only _Errors_ and log level `trace` logs would capture the entire stack trace. + +Usually, only _Errors_ and log level `trace` logs would capture the entire stack trace. By enabling this option **every** stack trace of every log message is going to be captured. ```typescript @@ -412,22 +426,22 @@ new Logger({ exposeStack: true }); ![tslog with a stack trace](https://raw.githubusercontent.com/fullstack-build/tslog/master/docs/assets/tslog_stacktrace.png) -> **Hint:** When working in an IDE like _WebStorm_ or an editor like _VSCode_ you can click on the path leading you directly to the position in your source code. +> **Hint:** When working in an IDE like _WebStorm_ or an editor like _VSCode_ you can click on the path leading you directly to the position in your source code. ##### `exposeErrorCodeFrame` ```default: true``` -A nice feature of `tslog` is to capture the _code frame_ around the error caught, showing the _exact_ location of the error. +A nice feature of `tslog` is to capture the _code frame_ around the error caught, showing the _exact_ location of the error. While it comes quite handy during development, it also means that the source file (*.js or *.ts) needs to be loaded. -When running in production, you probably want as much performance as possible and since errors are analyzed at a later point in time, -you may want to disable this feature. +When running in production, you probably want as much performance as possible and since errors are analyzed at a later point in time, +you may want to disable this feature. In order to keep the output clean and tidy, code frame does not follow into `node_modules`. ```typescript new Logger({ exposeErrorCodeFrame: false }); ``` -> **Hint:** By default 5 lines before and after the line with the error will be displayed. +> **Hint:** By default 5 lines before and after the line with the error will be displayed. > You can adjust this setting with `exposeErrorCodeFrameLinesBeforeAndAfter`. ![tslog with a code frame](https://raw.githubusercontent.com/fullstack-build/tslog/master/docs/assets/tslog_code_frame.png) @@ -436,7 +450,7 @@ new Logger({ exposeErrorCodeFrame: false }); ##### `ignoreStackLevels` ```default: 3``` -Defines how many stack levels should be ignored. +Defines how many stack levels should be ignored. `tslog` adds additional 3 layers to the stack and that the reason why the default is set to `3`. You can increase this number, if you want to add additional layers (e.g. a factory class or a facade). @@ -444,8 +458,8 @@ You can increase this number, if you want to add additional layers (e.g. a fact ##### `suppressStdOutput` ```default: false``` -It is possible to connect multiple _transports_ (external loggers) to `tslog` (see below). -In this case it might be useful to suppress all output. +It is possible to connect multiple _transports_ (external loggers) to `tslog` (see below). +In this case it might be useful to suppress all output. ```typescript new Logger({ suppressStdOutput: true }); @@ -454,15 +468,15 @@ new Logger({ suppressStdOutput: true }); ##### `overwriteConsole` ```default: false``` -`tslog` is designed to be used directly through its API. -However, there might be use cases, where you want to make sure to capture all logs, -even though they might occur in a library or somebody else's code. -Or maybe you prefer or used to work with `console`, like `console.log`, `console.warn` and so on. +`tslog` is designed to be used directly through its API. +However, there might be use cases, where you want to make sure to capture all logs, +even though they might occur in a library or somebody else's code. +Or maybe you prefer or used to work with `console`, like `console.log`, `console.warn` and so on. In this case, you can advise `tslog` to overwrite the default behavior of `console`. -> **Hint:** It is only possible to overwrite `console` once, so the last attempt wins. -> If you wish to do so, I would recommend to have a designated logger for this purpose. +> **Hint:** It is only possible to overwrite `console` once, so the last attempt wins. +> If you wish to do so, I would recommend to have a designated logger for this purpose. ```typescript new Logger({ name: "console", overwriteConsole: true }); @@ -481,36 +495,36 @@ _There is no `console.fatal`._ ##### `colorizePrettyLogs` ```default: true``` -By default `pretty` output is colorized with ANSI escape codes. If you prefer a plain output, you can disable the colorization with this setting. +By default `pretty` output is colorized with ANSI escape codes. If you prefer a plain output, you can disable the colorization with this setting. ##### `logLevelsColors` This setting allows you to overwrite the default log level colors of `tslog`. -Possible styles are: +Possible styles are: * Foreground colors * Background colors * Modifiers - + ##### `prettyInspectHighlightStyles` This setting allows you to overwrite the default colors of `tslog` used for the native Node.js `utils.inspect` interpolation. More Details: Customizing util.inspect colors -##### `dateTimePattern` +##### `dateTimePattern` ```default: "year-month-day hour:minute:second.millisecond"``` -Change the way `tslog` prints out the date. -Based on Intl.DateTimeFormat.formatToParts with additional milliseconds, you can use type as a placeholder. +Change the way `tslog` prints out the date. +Based on Intl.DateTimeFormat.formatToParts with additional milliseconds, you can use type as a placeholder. Available placeholders are: `day`, `dayPeriod`, `era`, `hour`, `literal`, `minute`, `month`, `second`, `millisecond`, `timeZoneName`, `weekday` and `year`. -##### `dateTimeTimezone` +##### `dateTimeTimezone` ```default: "utc" ``` Define in which timezone the date should be printed in. -Possible values are `utc` and IANA (Internet Assigned Numbers Authority) based timezones, e.g. `Europe/Berlin`, `Europe/Moscow` and so on. +Possible values are `utc` and IANA (Internet Assigned Numbers Authority) based timezones, e.g. `Europe/Berlin`, `Europe/Moscow` and so on. -> **Hint:** If you want to use your local time zone, you can set: +> **Hint:** If you want to use your local time zone, you can set: > `dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone` ##### `prefix` @@ -519,8 +533,8 @@ Possible values are `utc` and **Hint:** It will also mask keys if it encounters a matching pattern. +> **Hint:** It will also mask keys if it encounters a matching pattern. **`maskValuesOfKeys` is case sensitive!** @@ -625,7 +639,7 @@ let secretiveObject = { verySecretiveLogger.info(secretiveObject); // Output: -// INFO [SecretiveLogger] +// INFO [SecretiveLogger] // { // Authorization: '[***]', // regularString: 'I am just a regular string.', @@ -640,60 +654,60 @@ verySecretiveLogger.info(secretiveObject); > **Hint:** useful for API keys and other secrets (e.g. from ENVs). -##### `maskPlaceholder` +##### `maskPlaceholder` ```default: "[***]" ``` String to use for masking of secrets (s. `maskAnyRegEx` & `maskValuesOfKeys`) -##### `printLogMessageInNewLine` +##### `printLogMessageInNewLine` ```default: false ``` -By default `tslog` uses `tab` delimiters for separation of the meta information (date, log level, etc.) and the log parameters. -Since the meta information can become quite long, you may want to prefer to print the log attributes in a new line. +By default `tslog` uses `tab` delimiters for separation of the meta information (date, log level, etc.) and the log parameters. +Since the meta information can become quite long, you may want to prefer to print the log attributes in a new line. -##### `displayDateTime` +##### `displayDateTime` ```default: true ``` -Defines whether the date time should be displayed. +Defines whether the date time should be displayed. -##### `displayLogLevel` +##### `displayLogLevel` ```default: true ``` -Defines whether the log level should be displayed. +Defines whether the log level should be displayed. -##### `displayInstanceName` +##### `displayInstanceName` ```default: false ``` -Defines whether the instance name (e.g. host name) should be displayed. +Defines whether the instance name (e.g. host name) should be displayed. -##### `displayLoggerName` +##### `displayLoggerName` ```default: true ``` -Defines whether the optional logger name should be displayed. +Defines whether the optional logger name should be displayed. -##### `displayRequestId` +##### `displayRequestId` ```default: true ``` -Defines whether the `requestId` should be displayed, if set and available (s. `requestId`). +Defines whether the `requestId` should be displayed, if set and available (s. `requestId`). -##### `displayFunctionName` +##### `displayFunctionName` ```default: true ``` -Defines whether the class and method or function name should be displayed. +Defines whether the class and method or function name should be displayed. -##### `displayTypes` +##### `displayTypes` ```default: false ``` Defines whether type information (`typeof`) of every attribute passed to `tslog` should be displayed. -##### `displayFilePath` +##### `displayFilePath` ```default: hideNodeModulesOnly ``` -Defines whether file path and line should be displayed or not. -There are 3 possible settgins: +Defines whether file path and line should be displayed or not. +There are 3 possible settgins: * `hidden` * `displayAll` * `hideNodeModulesOnly` (default): This setting will hide all file paths containing `node_modules`. @@ -701,28 +715,28 @@ There are 3 possible settgins: ##### `stdOut` and `stdErr` -This both settings allow you to replace the default `stdOut` and `stdErr` _WriteStreams_. -However, this would lead to a colorized output. We use this setting mostly for testing purposes. -If you want to redirect the output or directly access any logged object, we advise you to **attach a transport** (see below). +This both settings allow you to replace the default `stdOut` and `stdErr` _WriteStreams_. +However, this would lead to a colorized output. We use this setting mostly for testing purposes. +If you want to redirect the output or directly access any logged object, we advise you to **attach a transport** (see below). #### Transports -`tslog` focuses on the one thing it does well: capturing logs. -Therefore, there is no build-in _file system_ logging, _log rotation_, or similar. -Per default all logs go to `stdOut` and `stdErr` respectively. +`tslog` focuses on the one thing it does well: capturing logs. +Therefore, there is no build-in _file system_ logging, _log rotation_, or similar. +Per default all logs go to `stdOut` and `stdErr` respectively. However, you can easily attach as many _transports_ as you wish, enabling you to do fancy stuff -like sending a message to _Slack_ or _Telegram_ in case of an urgent error. +like sending a message to _Slack_ or _Telegram_ in case of an urgent error. -When attaching a transport, you _must_ implement every log level. +When attaching a transport, you _must_ implement every log level. All of them could be potentially handled by the same function, though. Each _transport_ can have its own `minLevel`. -**Attached transports are also inherited to child loggers.** +**Attached transports are also inherited to child loggers.** ##### Simple transport example -Here is a very simple implementation used in our _jest_ tests: +Here is a very simple implementation used in our _jest_ tests: ```typescript import { ILogObject, Logger } from "tslog"; @@ -750,7 +764,7 @@ logger.attachTransport( ##### Storing logs in a file -Here is an example how to store all logs in a file. +Here is an example how to store all logs in a file. ```typescript import { ILogObject, Logger } from "tslog"; @@ -788,10 +802,10 @@ logger.warn("I am a warn log with a json object:", { foo: "bar" }); #### Helper ##### prettyError -Sometimes you just want to _pretty print_ an error without having to log it, or maybe just catch its call sites, or it's stack frame? If so, this helper is for you. -`prettyError` exposes all the awesomeness of `tslog` without the actual logging. A possible use case could be in a CLI, or other internal helper tools. +Sometimes you just want to _pretty print_ an error without having to log it, or maybe just catch its call sites, or it's stack frame? If so, this helper is for you. +`prettyError` exposes all the awesomeness of `tslog` without the actual logging. A possible use case could be in a CLI, or other internal helper tools. -Example: +Example: ```typescript const logger: Logger = new Logger(); const err: Error = new Error("Test Error"); @@ -809,7 +823,7 @@ logger.prettyError(err); * `std` - Which std should the output be printed to? _(default: stdErr)_ ##### printPrettyLog -If you just want to _pretty print_ an error on a custom output (for adding a new transport for example), +If you just want to _pretty print_ an error on a custom output (for adding a new transport for example), you can just call `logger.printPrettyLog(myStd, myLogObject)` where myStd is an instance of `IStd` (e.g. `process.stdout`, `process.stderr` or even a custom one, see example below): ```typescript diff --git a/package.json b/package.json index 93e5a142..c7e0bba8 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,9 @@ "name": "tslog", "version": "2.11.1", "description": "📝 Expressive TypeScript Logger for Node.js: Pretty errors, stack traces, code frames, and JSON output to attachable transports.", - "main": "dist/index", - "typings": "dist/index", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", "engines": { "node": ">=10" }, @@ -12,7 +13,7 @@ "ts-node-onchange": "onchange -i \"**/*.ts\" -- npm run ts-node", "start-ts": "npm run ts-node", "start": "node dist/example/index.js", - "build": "tsc", + "build": "tsc -b tsconfig.json tsconfig.esm.json tsconfig.types.json", "prepublishOnly": "npm run build && npm run test", "eslint": "eslint . --ext .ts", "eslint-fix": "eslint . --ext .ts --fix", @@ -80,8 +81,10 @@ "source-map-support": "^0.5.19" }, "files": [ - "dist" + "dist", + "src" ], + "sideEffects": false, "keywords": [ "logger", "typescript", diff --git a/src/Logger.ts b/src/Logger.ts new file mode 100644 index 00000000..609c7924 --- /dev/null +++ b/src/Logger.ts @@ -0,0 +1,18 @@ +import { wrapCallSite } from "source-map-support"; +import { ISettings, ISettingsParam } from "."; +import { LoggerWithoutCallSite } from "./LoggerWithoutCallSite"; + +/** + * 📝 Expressive TypeScript Logger for Node.js + * @public + */ +export class Logger extends LoggerWithoutCallSite { + /** + * @param settings - Configuration of the logger instance (all settings are optional with sane defaults) + * @param parentSettings - Used internally to + */ + public constructor(settings?: ISettingsParam, parentSettings?: ISettings) { + super(settings, parentSettings); + this._callSiteWrapper = wrapCallSite; + } +} \ No newline at end of file diff --git a/src/LoggerWithoutCallSite.ts b/src/LoggerWithoutCallSite.ts new file mode 100644 index 00000000..ad4f8f4a --- /dev/null +++ b/src/LoggerWithoutCallSite.ts @@ -0,0 +1,860 @@ + +import { hostname } from "os"; +import { normalize as fileNormalize } from "path"; +import { inspect, format } from "util"; + +import { + IErrorObject, + ILogObject, + ISettings, + ISettingsParam, + IStackFrame, + IStd, + TTransportLogger, + ITransportProvider, + TLogLevelName, + TLogLevelId, + ICodeFrame, + ILogObjectStringifiable, + TUtilsInspectColors, + IErrorObjectStringifiable, + IFullDateTimeFormatPart, +} from "./interfaces"; +import { LoggerHelper } from "./LoggerHelper"; +import { InspectOptions } from "util"; +import { Logger } from "./Logger"; + +/** + * 📝 Expressive TypeScript Logger for Node.js + * @public + */ +export class LoggerWithoutCallSite { + private readonly _logLevels: TLogLevelName[] = [ + "silly", + "trace", + "debug", + "info", + "warn", + "error", + "fatal", + ]; + + private readonly _minLevelToStdErr: number = 4; + private _parentOrDefaultSettings: ISettings; + private _mySettings: ISettingsParam = {}; + private _childLogger: Logger[] = []; + private _maskAnyRegExp: RegExp | undefined; + protected _callSiteWrapper: (frame: any) => any = (x) => x; + + /** + * @param settings - Configuration of the logger instance (all settings are optional with sane defaults) + * @param parentSettings - Used internally to + */ + public constructor(settings?: ISettingsParam, parentSettings?: ISettings) { + this._parentOrDefaultSettings = { + type: "pretty", + instanceName: hostname(), + name: undefined, + setCallerAsLoggerName: false, + requestId: undefined, + minLevel: "silly", + exposeStack: false, + exposeErrorCodeFrame: true, + exposeErrorCodeFrameLinesBeforeAndAfter: 5, + ignoreStackLevels: 3, + suppressStdOutput: false, + overwriteConsole: false, + colorizePrettyLogs: true, + logLevelsColors: { + 0: "whiteBright", + 1: "white", + 2: "greenBright", + 3: "blueBright", + 4: "yellowBright", + 5: "redBright", + 6: "magentaBright", + }, + prettyInspectHighlightStyles: { + special: "cyan", + number: "green", + bigint: "green", + boolean: "yellow", + undefined: "red", + null: "red", + string: "red", + symbol: "green", + date: "magenta", + name: "white", + regexp: "red", + module: "underline", + }, + prettyInspectOptions: { + colors: true, + compact: false, + depth: Infinity, + }, + jsonInspectOptions: { + colors: false, + compact: true, + depth: Infinity, + }, + dateTimePattern: "year-month-day hour:minute:second.millisecond", + // local timezone: Intl.DateTimeFormat().resolvedOptions().timeZone + dateTimeTimezone: "utc", + + prefix: [], + maskValuesOfKeys: ["password"], + maskAnyRegEx: [], + maskPlaceholder: "[***]", + + printLogMessageInNewLine: false, + + // display settings + displayDateTime: true, + displayLogLevel: true, + displayInstanceName: false, + displayLoggerName: true, + displayRequestId: true, + displayFilePath: "hideNodeModulesOnly", + displayFunctionName: true, + displayTypes: false, + + stdOut: process.stdout, + stdErr: process.stderr, + attachedTransports: [], + }; + const mySettings: ISettingsParam = settings != null ? settings : {}; + this.setSettings(mySettings, parentSettings); + + LoggerHelper.initErrorToJsonHelper(); + } + + /** Readonly settings of the current logger instance. Used for testing. */ + public get settings(): ISettings { + const myPrefix: unknown[] = + this._mySettings.prefix != null ? this._mySettings.prefix : []; + return { + ...this._parentOrDefaultSettings, + ...this._mySettings, + prefix: [...this._parentOrDefaultSettings.prefix, ...myPrefix], + }; + } + + /** + * Change settings during runtime + * Changes will be propagated to potential child loggers + * + * @param settings - Settings to overwrite with. Only this settings will be overwritten, rest will remain the same. + * @param parentSettings - INTERNAL USE: Is called by a parent logger to propagate new settings. + */ + public setSettings( + settings: ISettingsParam, + parentSettings?: ISettings + ): ISettings { + this._mySettings = { + ...this._mySettings, + ...settings, + }; + + this._mySettings.name = + this._mySettings.name ?? + (this._mySettings.setCallerAsLoggerName + ? LoggerHelper.getCallSites()?.[0]?.getTypeName() ?? + LoggerHelper.getCallSites()?.[0]?.getFunctionName() ?? + undefined + : undefined); + + if (parentSettings != null) { + this._parentOrDefaultSettings = { + ...this._parentOrDefaultSettings, + ...parentSettings, + }; + } + + this._maskAnyRegExp = + this.settings.maskAnyRegEx?.length > 0 + ? new RegExp(Object.values(this.settings.maskAnyRegEx).join("|"), "g") + : undefined; + + LoggerHelper.setUtilsInspectStyles( + this.settings.prettyInspectHighlightStyles + ); + + if (this.settings.overwriteConsole) { + LoggerHelper.overwriteConsole(this, this._handleLog); + } + + this._childLogger.forEach((childLogger: Logger) => { + childLogger.setSettings({}, this.settings); + }); + + return this.settings; + } + + /** + * Returns a child logger based on the current instance with inherited settings + * + * @param settings - Overwrite settings inherited from parent logger + */ + public getChildLogger(settings?: ISettingsParam): Logger { + const childSettings: ISettings = { + ...this.settings, + }; + const childLogger: Logger = new (this.constructor as any)(settings, childSettings); + this._childLogger.push(childLogger); + return childLogger; + } + + /** + * Attaches external Loggers, e.g. external log services, file system, database + * + * @param transportLogger - External logger to be attached. Must implement all log methods. + * @param minLevel - Minimum log level to be forwarded to this attached transport logger. (e.g. debug) + */ + public attachTransport( + transportLogger: TTransportLogger<(message: ILogObject) => void>, + minLevel: TLogLevelName = "silly" + ): void { + this.settings.attachedTransports.push({ + minLevel, + transportLogger, + }); + } + + /** + * Logs a silly message. + * @param args - Multiple log attributes that should be logged out. + */ + public silly(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["silly", args]); + } + + /** + * Logs a trace message. + * @param args - Multiple log attributes that should be logged out. + */ + public trace(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["trace", args, true]); + } + + /** + * Logs a debug message. + * @param args - Multiple log attributes that should be logged out. + */ + public debug(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["debug", args]); + } + + /** + * Logs an info message. + * @param args - Multiple log attributes that should be logged out. + */ + public info(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["info", args]); + } + + /** + * Logs a warn message. + * @param args - Multiple log attributes that should be logged out. + */ + public warn(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["warn", args]); + } + + /** + * Logs an error message. + * @param args - Multiple log attributes that should be logged out. + */ + public error(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["error", args]); + } + + /** + * Logs a fatal message. + * @param args - Multiple log attributes that should be logged out. + */ + public fatal(...args: unknown[]): ILogObject { + return this._handleLog.apply(this, ["fatal", args]); + } + + /** + * Helper: Pretty print error without logging it + * @param error - Error object + * @param print - Print the error or return only? (default: true) + * @param exposeErrorCodeFrame - Should the code frame be exposed? (default: true) + * @param exposeStackTrace - Should the stack trace be exposed? (default: true) + * @param stackOffset - Offset lines of the stack trace (default: 0) + * @param stackLimit - Limit number of lines of the stack trace (default: Infinity) + * @param std - Which std should the output be printed to? (default: stdErr) + */ + public prettyError( + error: Error, + print: boolean = true, + exposeErrorCodeFrame: boolean = true, + exposeStackTrace: boolean = true, + stackOffset: number = 0, + stackLimit: number = Infinity, + std: IStd = this.settings.stdErr + ): IErrorObject { + const errorObject: IErrorObject = this._buildErrorObject( + error, + exposeErrorCodeFrame, + stackOffset, + stackLimit + ); + if (print) { + this._printPrettyError(std, errorObject, exposeStackTrace); + } + return errorObject; + } + + private _handleLog( + logLevel: TLogLevelName, + logArguments: unknown[], + exposeStack: boolean = this.settings.exposeStack + ): ILogObject { + const logObject: ILogObject = this._buildLogObject( + logLevel, + logArguments, + exposeStack + ); + + if ( + !this.settings.suppressStdOutput && + logObject.logLevelId >= this._logLevels.indexOf(this.settings.minLevel) + ) { + const std: IStd = + logObject.logLevelId < this._minLevelToStdErr + ? this.settings.stdOut + : this.settings.stdErr; + + if (this.settings.type === "pretty") { + this.printPrettyLog(std, logObject); + } else if (this.settings.type === "json") { + this._printJsonLog(std, logObject); + } else { + // don't print (e.g. "hidden") + } + } + + this.settings.attachedTransports.forEach( + (transport: ITransportProvider) => { + if ( + logObject.logLevelId >= + Object.values(this._logLevels).indexOf(transport.minLevel) + ) { + transport.transportLogger[logLevel](logObject); + } + } + ); + + return logObject; + } + + private _buildLogObject( + logLevel: TLogLevelName, + logArguments: unknown[], + exposeStack: boolean = true + ): ILogObject { + const callSites: NodeJS.CallSite[] = LoggerHelper.getCallSites(); + const relevantCallSites: NodeJS.CallSite[] = callSites.splice( + this.settings.ignoreStackLevels + ); + const stackFrame: NodeJS.CallSite = this._callSiteWrapper(relevantCallSites[0]); + const stackFrameObject: IStackFrame = LoggerHelper.toStackFrameObject( + stackFrame + ); + + const requestId: string | undefined = + this.settings.requestId instanceof Function + ? this.settings.requestId() + : this.settings.requestId; + + const logObject: ILogObject = { + instanceName: this.settings.instanceName, + loggerName: this.settings.name, + hostname: hostname(), + requestId, + date: new Date(), + logLevel: logLevel, + logLevelId: this._logLevels.indexOf(logLevel) as TLogLevelId, + filePath: stackFrameObject.filePath, + fullFilePath: stackFrameObject.fullFilePath, + fileName: stackFrameObject.fileName, + lineNumber: stackFrameObject.lineNumber, + columnNumber: stackFrameObject.columnNumber, + isConstructor: stackFrameObject.isConstructor, + functionName: stackFrameObject.functionName, + typeName: stackFrameObject.typeName, + methodName: stackFrameObject.methodName, + argumentsArray: [], + toJSON: () => this._logObjectToJson(logObject), + }; + + const logArgumentsWithPrefix: unknown[] = [ + ...this.settings.prefix, + ...logArguments, + ]; + + logArgumentsWithPrefix.forEach((arg: unknown) => { + if (arg != null && typeof arg === "object" && LoggerHelper.isError(arg)) { + logObject.argumentsArray.push( + this._buildErrorObject( + arg as Error, + this.settings.exposeErrorCodeFrame + ) + ); + } else { + logObject.argumentsArray.push(arg); + } + }); + + if (exposeStack) { + logObject.stack = this._toStackObjectArray(relevantCallSites); + } + + return logObject; + } + + private _buildErrorObject( + error: Error, + exposeErrorCodeFrame: boolean = true, + stackOffset: number = 0, + stackLimit: number = Infinity + ): IErrorObject { + const errorCallSites: NodeJS.CallSite[] = LoggerHelper.getCallSites(error); + stackOffset = stackOffset > -1 ? stackOffset : 0; + + const relevantCallSites: NodeJS.CallSite[] = + (errorCallSites?.splice && errorCallSites.splice(stackOffset)) ?? []; + + stackLimit = stackLimit > -1 ? stackLimit : 0; + if (stackLimit < Infinity) { + relevantCallSites.length = stackLimit; + } + + const errorObject: IErrorObject = JSON.parse(JSON.stringify(error)); + errorObject.nativeError = error; + errorObject.details = { ...error }; + errorObject.name = errorObject.name ?? "Error"; + errorObject.isError = true; + errorObject.stack = this._toStackObjectArray(relevantCallSites); + if (errorObject.stack.length > 0) { + const errorCallSite: IStackFrame = LoggerHelper.toStackFrameObject( + this._callSiteWrapper(relevantCallSites[0]) + ); + if (exposeErrorCodeFrame && errorCallSite.lineNumber != null) { + if (errorCallSite.fullFilePath.indexOf("node_modules") < 0) { + errorObject.codeFrame = LoggerHelper._getCodeFrame( + errorCallSite.fullFilePath, + errorCallSite.lineNumber, + errorCallSite.columnNumber, + this.settings.exposeErrorCodeFrameLinesBeforeAndAfter + ); + } + } + } + + return errorObject; + } + + private _toStackObjectArray(jsStack: NodeJS.CallSite[]): IStackFrame[] { + const stackFrame: IStackFrame[] = Object.values(jsStack).reduce( + (stackFrameObj: IStackFrame[], callsite: NodeJS.CallSite) => { + stackFrameObj.push( + LoggerHelper.toStackFrameObject(this._callSiteWrapper(callsite)) + ); + return stackFrameObj; + }, + [] + ); + return stackFrame; + } + + /** + * Pretty print the log object to the designated output. + * + * @param std - output where to pretty print the object + * @param logObject - object to pretty print + **/ + public printPrettyLog(std: IStd, logObject: ILogObject): void { + if (this.settings.displayDateTime === true) { + const dateTimeParts: IFullDateTimeFormatPart[] = [ + ...(new Intl.DateTimeFormat("en", { + weekday: undefined, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + timeZone: this.settings.dateTimeTimezone, + }).formatToParts(logObject.date) as IFullDateTimeFormatPart[]), + { + type: "millisecond", + value: logObject.date.getMilliseconds().toFixed(3), + } as IFullDateTimeFormatPart, + ]; + + const nowStr: string = dateTimeParts.reduce( + (prevStr: string, thisStr: IFullDateTimeFormatPart) => + prevStr.replace(thisStr.type, thisStr.value), + this.settings.dateTimePattern + ); + std.write( + LoggerHelper.styleString( + ["gray"], + `${nowStr}\t`, + this.settings.colorizePrettyLogs + ) + ); + } + + if (this.settings.displayLogLevel) { + const colorName: TUtilsInspectColors = this.settings.logLevelsColors[ + logObject.logLevelId + ]; + + std.write( + LoggerHelper.styleString( + [colorName, "bold"], + ` ${logObject.logLevel.toUpperCase()} `, + this.settings.colorizePrettyLogs + ) + "\t" + ); + } + + const loggerName: string = + this.settings.displayLoggerName === true && logObject.loggerName != null + ? logObject.loggerName + : ""; + + const instanceName: string = + this.settings.displayInstanceName === true && + this.settings.instanceName != null + ? `@${this.settings.instanceName}` + : ""; + + const traceId: string = + this.settings.displayRequestId === true && logObject.requestId != null + ? `:${logObject.requestId}` + : ""; + + const name: string = + (loggerName + instanceName + traceId).length > 0 + ? loggerName + instanceName + traceId + : ""; + + const functionName: string = + this.settings.displayFunctionName === true + ? logObject.isConstructor + ? ` ${logObject.typeName}.constructor` + : logObject.methodName != null + ? ` ${logObject.typeName}.${logObject.methodName}` + : logObject.functionName != null + ? ` ${logObject.functionName}` + : "" + : ""; + + let fileLocation: string = ""; + if ( + this.settings.displayFilePath === "displayAll" || + (this.settings.displayFilePath === "hideNodeModulesOnly" && + logObject.filePath.indexOf("node_modules") < 0) + ) { + fileLocation = `${logObject.filePath}:${logObject.lineNumber}`; + } + const concatenatedMetaLine: string = [name, fileLocation, functionName] + .join(" ") + .replace(/\s\s+/g, " ") + .trim(); + if (concatenatedMetaLine.length > 0) { + std.write( + LoggerHelper.styleString( + ["gray"], + `[${concatenatedMetaLine}]`, + this.settings.colorizePrettyLogs + ) + " \t" + ); + + if (this.settings.printLogMessageInNewLine === false) { + std.write(" \t"); + } else { + std.write("\n"); + } + } + + logObject.argumentsArray.forEach((argument: unknown | IErrorObject) => { + const typeStr: string = + this.settings.displayTypes === true + ? LoggerHelper.styleString( + ["grey", "bold"], + typeof argument + ":", + this.settings.colorizePrettyLogs + ) + " " + : ""; + + const errorObject: IErrorObject = argument as IErrorObject; + if (typeof argument === "object" && errorObject?.isError === true) { + this._printPrettyError(std, errorObject); + } else if ( + typeof argument === "object" && + errorObject?.isError !== true + ) { + std.write( + "\n" + + typeStr + + this._inspectAndHideSensitive( + argument, + this.settings.prettyInspectOptions + ) + ); + } else { + std.write(typeStr + this._formatAndHideSensitive(argument) + " "); + } + }); + std.write("\n"); + + if (logObject.stack != null) { + std.write( + LoggerHelper.styleString( + ["underline", "bold"], + "log stack:\n", + this.settings.colorizePrettyLogs + ) + ); + + this._printPrettyStack(std, logObject.stack); + } + } + + private _printPrettyError( + std: IStd, + errorObject: IErrorObject, + printStackTrace: boolean = true + ): void { + std.write( + "\n" + + LoggerHelper.styleString( + ["bgRed", "whiteBright", "bold"], + ` ${errorObject.name} `, + this.settings.colorizePrettyLogs + ) + + (errorObject.message != null + ? `\t${this._formatAndHideSensitive(errorObject.message)}` + : "") + ); + + if (Object.values(errorObject.details).length > 0) { + std.write( + LoggerHelper.styleString( + ["underline", "bold"], + "\ndetails:", + this.settings.colorizePrettyLogs + ) + ); + std.write( + "\n" + + this._inspectAndHideSensitive( + errorObject.details, + this.settings.prettyInspectOptions + ) + ); + } + + if (printStackTrace === true && errorObject?.stack?.length > 0) { + std.write( + LoggerHelper.styleString( + ["underline", "bold"], + "\nerror stack:", + this.settings.colorizePrettyLogs + ) + ); + + this._printPrettyStack(std, errorObject.stack); + } + if (errorObject.codeFrame != null) { + this._printPrettyCodeFrame(std, errorObject.codeFrame); + } + } + + private _printPrettyStack(std: IStd, stackObjectArray: IStackFrame[]): void { + std.write("\n"); + Object.values(stackObjectArray).forEach((stackObject: IStackFrame) => { + std.write( + LoggerHelper.styleString( + ["gray"], + "• ", + this.settings.colorizePrettyLogs + ) + ); + + if (stackObject.fileName != null) { + std.write( + LoggerHelper.styleString( + ["yellowBright"], + stackObject.fileName, + this.settings.colorizePrettyLogs + ) + ); + } + + if (stackObject.lineNumber != null) { + std.write( + LoggerHelper.styleString( + ["gray"], + ":", + this.settings.colorizePrettyLogs + ) + ); + std.write( + LoggerHelper.styleString( + ["yellow"], + stackObject.lineNumber, + this.settings.colorizePrettyLogs + ) + ); + } + + std.write( + LoggerHelper.styleString( + ["white"], + " " + (stackObject.functionName ?? ""), + this.settings.colorizePrettyLogs + ) + ); + + if ( + stackObject.filePath != null && + stackObject.lineNumber != null && + stackObject.columnNumber != null + ) { + std.write("\n "); + std.write( + fileNormalize( + LoggerHelper.styleString( + ["gray"], + `${stackObject.filePath}:${stackObject.lineNumber}:${stackObject.columnNumber}`, + this.settings.colorizePrettyLogs + ) + ) + ); + } + std.write("\n\n"); + }); + } + + private _printPrettyCodeFrame(std: IStd, codeFrame: ICodeFrame): void { + std.write( + LoggerHelper.styleString( + ["underline", "bold"], + "code frame:\n", + this.settings.colorizePrettyLogs + ) + ); + + let lineNumber: number = codeFrame.firstLineNumber; + codeFrame.linesBefore.forEach((line: string) => { + std.write(` ${LoggerHelper.lineNumberTo3Char(lineNumber)} | ${line}\n`); + lineNumber++; + }); + + std.write( + LoggerHelper.styleString(["red"], ">", this.settings.colorizePrettyLogs) + + " " + + LoggerHelper.styleString( + ["bgRed", "whiteBright"], + LoggerHelper.lineNumberTo3Char(lineNumber), + this.settings.colorizePrettyLogs + ) + + " | " + + LoggerHelper.styleString( + ["yellow"], + codeFrame.relevantLine, + this.settings.colorizePrettyLogs + ) + + "\n" + ); + lineNumber++; + + if (codeFrame.columnNumber != null) { + const positionMarker: string = + new Array(codeFrame.columnNumber + 8).join(" ") + `^`; + std.write( + LoggerHelper.styleString( + ["red"], + positionMarker, + this.settings.colorizePrettyLogs + ) + "\n" + ); + } + + codeFrame.linesAfter.forEach((line: string) => { + std.write(` ${LoggerHelper.lineNumberTo3Char(lineNumber)} | ${line}\n`); + lineNumber++; + }); + } + + private _logObjectToJson(logObject: ILogObject): ILogObjectStringifiable { + return { + ...logObject, + argumentsArray: logObject.argumentsArray.map( + (argument: unknown | IErrorObject) => { + const errorObject: IErrorObject = argument as IErrorObject; + if (typeof argument === "object" && errorObject.isError) { + return { + ...errorObject, + nativeError: undefined, + errorString: this._formatAndHideSensitive( + errorObject.nativeError + ), + } as IErrorObjectStringifiable; + } else if (typeof argument === "object") { + return this._inspectAndHideSensitive( + argument, + this.settings.jsonInspectOptions + ); + } else { + return this._formatAndHideSensitive(argument); + } + } + ), + }; + } + + private _printJsonLog(std: IStd, logObject: ILogObject): void { + std.write(JSON.stringify(logObject) + "\n"); + } + + private _inspectAndHideSensitive( + object: object | null, + options: InspectOptions + ): string { + const maskedObject = this._maskValuesOfKeys(object); + return this._maskAny(inspect(maskedObject, options)); + } + + private _formatAndHideSensitive( + formatParam: unknown, + ...param: unknown[] + ): string { + return this._maskAny(format(formatParam, ...param)); + } + + private _maskValuesOfKeys(object: object | null) { + return LoggerHelper.logObjectMaskValuesOfKeys( + object, + this.settings.maskValuesOfKeys, + this.settings.maskPlaceholder + ); + } + + private _maskAny(str: string) { + const formattedStr = str; + + return this._maskAnyRegExp != null + ? formattedStr.replace(this._maskAnyRegExp, this.settings.maskPlaceholder) + : formattedStr; + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8a4b372b..63730c26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,35 +3,6 @@ * @packageDocumentation */ -import { hostname } from "os"; -import { normalize as fileNormalize } from "path"; -import { inspect, format } from "util"; -import { wrapCallSite } from "source-map-support"; - -import { - ILogLevel, - IErrorObject, - ILogObject, - ISettings, - ISettingsParam, - IStackFrame, - IStd, - TRequestIdFunction, - TTransportLogger, - ITransportProvider, - TLogLevelName, - TLogLevelId, - IHighlightStyles, - TLogLevelColor, - ICodeFrame, - ILogObjectStringifiable, - TUtilsInspectColors, - IErrorObjectStringifiable, - IFullDateTimeFormatPart, -} from "./interfaces"; -import { LoggerHelper } from "./LoggerHelper"; -import { InspectOptions } from "util"; - export { ILogLevel, TTransportLogger, @@ -50,838 +21,8 @@ export { TUtilsInspectColors, ISettings, ICodeFrame, -}; - -/** - * 📝 Expressive TypeScript Logger for Node.js - * @public - */ -export class Logger { - private readonly _logLevels: TLogLevelName[] = [ - "silly", - "trace", - "debug", - "info", - "warn", - "error", - "fatal", - ]; - - private readonly _minLevelToStdErr: number = 4; - private _parentOrDefaultSettings: ISettings; - private _mySettings: ISettingsParam = {}; - private _childLogger: Logger[] = []; - private _maskAnyRegExp: RegExp | undefined; - - /** - * @param settings - Configuration of the logger instance (all settings are optional with sane defaults) - * @param parentSettings - Used internally to - */ - public constructor(settings?: ISettingsParam, parentSettings?: ISettings) { - this._parentOrDefaultSettings = { - type: "pretty", - instanceName: hostname(), - name: undefined, - setCallerAsLoggerName: false, - requestId: undefined, - minLevel: "silly", - exposeStack: false, - exposeErrorCodeFrame: true, - exposeErrorCodeFrameLinesBeforeAndAfter: 5, - ignoreStackLevels: 3, - suppressStdOutput: false, - overwriteConsole: false, - colorizePrettyLogs: true, - logLevelsColors: { - 0: "whiteBright", - 1: "white", - 2: "greenBright", - 3: "blueBright", - 4: "yellowBright", - 5: "redBright", - 6: "magentaBright", - }, - prettyInspectHighlightStyles: { - special: "cyan", - number: "green", - bigint: "green", - boolean: "yellow", - undefined: "red", - null: "red", - string: "red", - symbol: "green", - date: "magenta", - name: "white", - regexp: "red", - module: "underline", - }, - prettyInspectOptions: { - colors: true, - compact: false, - depth: Infinity, - }, - jsonInspectOptions: { - colors: false, - compact: true, - depth: Infinity, - }, - dateTimePattern: "year-month-day hour:minute:second.millisecond", - // local timezone: Intl.DateTimeFormat().resolvedOptions().timeZone - dateTimeTimezone: "utc", - - prefix: [], - maskValuesOfKeys: ["password"], - maskAnyRegEx: [], - maskPlaceholder: "[***]", - - printLogMessageInNewLine: false, - - // display settings - displayDateTime: true, - displayLogLevel: true, - displayInstanceName: false, - displayLoggerName: true, - displayRequestId: true, - displayFilePath: "hideNodeModulesOnly", - displayFunctionName: true, - displayTypes: false, - - stdOut: process.stdout, - stdErr: process.stderr, - attachedTransports: [], - }; - const mySettings: ISettingsParam = settings != null ? settings : {}; - this.setSettings(mySettings, parentSettings); - - LoggerHelper.initErrorToJsonHelper(); - } - - /** Readonly settings of the current logger instance. Used for testing. */ - public get settings(): ISettings { - const myPrefix: unknown[] = - this._mySettings.prefix != null ? this._mySettings.prefix : []; - return { - ...this._parentOrDefaultSettings, - ...this._mySettings, - prefix: [...this._parentOrDefaultSettings.prefix, ...myPrefix], - }; - } - - /** - * Change settings during runtime - * Changes will be propagated to potential child loggers - * - * @param settings - Settings to overwrite with. Only this settings will be overwritten, rest will remain the same. - * @param parentSettings - INTERNAL USE: Is called by a parent logger to propagate new settings. - */ - public setSettings( - settings: ISettingsParam, - parentSettings?: ISettings - ): ISettings { - this._mySettings = { - ...this._mySettings, - ...settings, - }; - - this._mySettings.name = - this._mySettings.name ?? - (this._mySettings.setCallerAsLoggerName - ? LoggerHelper.getCallSites()?.[0]?.getTypeName() ?? - LoggerHelper.getCallSites()?.[0]?.getFunctionName() ?? - undefined - : undefined); - - if (parentSettings != null) { - this._parentOrDefaultSettings = { - ...this._parentOrDefaultSettings, - ...parentSettings, - }; - } - - this._maskAnyRegExp = - this.settings.maskAnyRegEx?.length > 0 - ? new RegExp(Object.values(this.settings.maskAnyRegEx).join("|"), "g") - : undefined; - - LoggerHelper.setUtilsInspectStyles( - this.settings.prettyInspectHighlightStyles - ); - - if (this.settings.overwriteConsole) { - LoggerHelper.overwriteConsole(this, this._handleLog); - } - - this._childLogger.forEach((childLogger: Logger) => { - childLogger.setSettings({}, this.settings); - }); - - return this.settings; - } - - /** - * Returns a child logger based on the current instance with inherited settings - * - * @param settings - Overwrite settings inherited from parent logger - */ - public getChildLogger(settings?: ISettingsParam): Logger { - const childSettings: ISettings = { - ...this.settings, - }; - const childLogger: Logger = new Logger(settings, childSettings); - this._childLogger.push(childLogger); - return childLogger; - } - - /** - * Attaches external Loggers, e.g. external log services, file system, database - * - * @param transportLogger - External logger to be attached. Must implement all log methods. - * @param minLevel - Minimum log level to be forwarded to this attached transport logger. (e.g. debug) - */ - public attachTransport( - transportLogger: TTransportLogger<(message: ILogObject) => void>, - minLevel: TLogLevelName = "silly" - ): void { - this.settings.attachedTransports.push({ - minLevel, - transportLogger, - }); - } - - /** - * Logs a silly message. - * @param args - Multiple log attributes that should be logged out. - */ - public silly(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["silly", args]); - } - - /** - * Logs a trace message. - * @param args - Multiple log attributes that should be logged out. - */ - public trace(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["trace", args, true]); - } - - /** - * Logs a debug message. - * @param args - Multiple log attributes that should be logged out. - */ - public debug(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["debug", args]); - } - - /** - * Logs an info message. - * @param args - Multiple log attributes that should be logged out. - */ - public info(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["info", args]); - } - - /** - * Logs a warn message. - * @param args - Multiple log attributes that should be logged out. - */ - public warn(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["warn", args]); - } - - /** - * Logs an error message. - * @param args - Multiple log attributes that should be logged out. - */ - public error(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["error", args]); - } - - /** - * Logs a fatal message. - * @param args - Multiple log attributes that should be logged out. - */ - public fatal(...args: unknown[]): ILogObject { - return this._handleLog.apply(this, ["fatal", args]); - } - - /** - * Helper: Pretty print error without logging it - * @param error - Error object - * @param print - Print the error or return only? (default: true) - * @param exposeErrorCodeFrame - Should the code frame be exposed? (default: true) - * @param exposeStackTrace - Should the stack trace be exposed? (default: true) - * @param stackOffset - Offset lines of the stack trace (default: 0) - * @param stackLimit - Limit number of lines of the stack trace (default: Infinity) - * @param std - Which std should the output be printed to? (default: stdErr) - */ - public prettyError( - error: Error, - print: boolean = true, - exposeErrorCodeFrame: boolean = true, - exposeStackTrace: boolean = true, - stackOffset: number = 0, - stackLimit: number = Infinity, - std: IStd = this.settings.stdErr - ): IErrorObject { - const errorObject: IErrorObject = this._buildErrorObject( - error, - exposeErrorCodeFrame, - stackOffset, - stackLimit - ); - if (print) { - this._printPrettyError(std, errorObject, exposeStackTrace); - } - return errorObject; - } - - private _handleLog( - logLevel: TLogLevelName, - logArguments: unknown[], - exposeStack: boolean = this.settings.exposeStack - ): ILogObject { - const logObject: ILogObject = this._buildLogObject( - logLevel, - logArguments, - exposeStack - ); - - if ( - !this.settings.suppressStdOutput && - logObject.logLevelId >= this._logLevels.indexOf(this.settings.minLevel) - ) { - const std: IStd = - logObject.logLevelId < this._minLevelToStdErr - ? this.settings.stdOut - : this.settings.stdErr; - - if (this.settings.type === "pretty") { - this.printPrettyLog(std, logObject); - } else if (this.settings.type === "json") { - this._printJsonLog(std, logObject); - } else { - // don't print (e.g. "hidden") - } - } - - this.settings.attachedTransports.forEach( - (transport: ITransportProvider) => { - if ( - logObject.logLevelId >= - Object.values(this._logLevels).indexOf(transport.minLevel) - ) { - transport.transportLogger[logLevel](logObject); - } - } - ); - - return logObject; - } - - private _buildLogObject( - logLevel: TLogLevelName, - logArguments: unknown[], - exposeStack: boolean = true - ): ILogObject { - const callSites: NodeJS.CallSite[] = LoggerHelper.getCallSites(); - const relevantCallSites: NodeJS.CallSite[] = callSites.splice( - this.settings.ignoreStackLevels - ); - const stackFrame: NodeJS.CallSite = wrapCallSite(relevantCallSites[0]); - const stackFrameObject: IStackFrame = LoggerHelper.toStackFrameObject( - stackFrame - ); - - const requestId: string | undefined = - this.settings.requestId instanceof Function - ? this.settings.requestId() - : this.settings.requestId; - - const logObject: ILogObject = { - instanceName: this.settings.instanceName, - loggerName: this.settings.name, - hostname: hostname(), - requestId, - date: new Date(), - logLevel: logLevel, - logLevelId: this._logLevels.indexOf(logLevel) as TLogLevelId, - filePath: stackFrameObject.filePath, - fullFilePath: stackFrameObject.fullFilePath, - fileName: stackFrameObject.fileName, - lineNumber: stackFrameObject.lineNumber, - columnNumber: stackFrameObject.columnNumber, - isConstructor: stackFrameObject.isConstructor, - functionName: stackFrameObject.functionName, - typeName: stackFrameObject.typeName, - methodName: stackFrameObject.methodName, - argumentsArray: [], - toJSON: () => this._logObjectToJson(logObject), - }; - - const logArgumentsWithPrefix: unknown[] = [ - ...this.settings.prefix, - ...logArguments, - ]; - - logArgumentsWithPrefix.forEach((arg: unknown) => { - if (arg != null && typeof arg === "object" && LoggerHelper.isError(arg)) { - logObject.argumentsArray.push( - this._buildErrorObject( - arg as Error, - this.settings.exposeErrorCodeFrame - ) - ); - } else { - logObject.argumentsArray.push(arg); - } - }); - - if (exposeStack) { - logObject.stack = this._toStackObjectArray(relevantCallSites); - } - - return logObject; - } - - private _buildErrorObject( - error: Error, - exposeErrorCodeFrame: boolean = true, - stackOffset: number = 0, - stackLimit: number = Infinity - ): IErrorObject { - const errorCallSites: NodeJS.CallSite[] = LoggerHelper.getCallSites(error); - stackOffset = stackOffset > -1 ? stackOffset : 0; - - const relevantCallSites: NodeJS.CallSite[] = - (errorCallSites?.splice && errorCallSites.splice(stackOffset)) ?? []; - - stackLimit = stackLimit > -1 ? stackLimit : 0; - if (stackLimit < Infinity) { - relevantCallSites.length = stackLimit; - } - - const errorObject: IErrorObject = JSON.parse(JSON.stringify(error)); - errorObject.nativeError = error; - errorObject.details = { ...error }; - errorObject.name = errorObject.name ?? "Error"; - errorObject.isError = true; - errorObject.stack = this._toStackObjectArray(relevantCallSites); - if (errorObject.stack.length > 0) { - const errorCallSite: IStackFrame = LoggerHelper.toStackFrameObject( - wrapCallSite(relevantCallSites[0]) - ); - if (exposeErrorCodeFrame && errorCallSite.lineNumber != null) { - if (errorCallSite.fullFilePath.indexOf("node_modules") < 0) { - errorObject.codeFrame = LoggerHelper._getCodeFrame( - errorCallSite.fullFilePath, - errorCallSite.lineNumber, - errorCallSite.columnNumber, - this.settings.exposeErrorCodeFrameLinesBeforeAndAfter - ); - } - } - } - - return errorObject; - } - - private _toStackObjectArray(jsStack: NodeJS.CallSite[]): IStackFrame[] { - const stackFrame: IStackFrame[] = Object.values(jsStack).reduce( - (stackFrameObj: IStackFrame[], callsite: NodeJS.CallSite) => { - stackFrameObj.push( - LoggerHelper.toStackFrameObject(wrapCallSite(callsite)) - ); - return stackFrameObj; - }, - [] - ); - return stackFrame; - } - - /** - * Pretty print the log object to the designated output. - * - * @param std - output where to pretty print the object - * @param logObject - object to pretty print - **/ - public printPrettyLog(std: IStd, logObject: ILogObject): void { - if (this.settings.displayDateTime === true) { - const dateTimeParts: IFullDateTimeFormatPart[] = [ - ...(new Intl.DateTimeFormat("en", { - weekday: undefined, - year: "numeric", - month: "2-digit", - day: "2-digit", - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - timeZone: this.settings.dateTimeTimezone, - }).formatToParts(logObject.date) as IFullDateTimeFormatPart[]), - { - type: "millisecond", - value: logObject.date.getMilliseconds().toFixed(3), - } as IFullDateTimeFormatPart, - ]; - - const nowStr: string = dateTimeParts.reduce( - (prevStr: string, thisStr: IFullDateTimeFormatPart) => - prevStr.replace(thisStr.type, thisStr.value), - this.settings.dateTimePattern - ); - std.write( - LoggerHelper.styleString( - ["gray"], - `${nowStr}\t`, - this.settings.colorizePrettyLogs - ) - ); - } - - if (this.settings.displayLogLevel) { - const colorName: TUtilsInspectColors = this.settings.logLevelsColors[ - logObject.logLevelId - ]; - - std.write( - LoggerHelper.styleString( - [colorName, "bold"], - ` ${logObject.logLevel.toUpperCase()} `, - this.settings.colorizePrettyLogs - ) + "\t" - ); - } - - const loggerName: string = - this.settings.displayLoggerName === true && logObject.loggerName != null - ? logObject.loggerName - : ""; - - const instanceName: string = - this.settings.displayInstanceName === true && - this.settings.instanceName != null - ? `@${this.settings.instanceName}` - : ""; - - const traceId: string = - this.settings.displayRequestId === true && logObject.requestId != null - ? `:${logObject.requestId}` - : ""; - - const name: string = - (loggerName + instanceName + traceId).length > 0 - ? loggerName + instanceName + traceId - : ""; - - const functionName: string = - this.settings.displayFunctionName === true - ? logObject.isConstructor - ? ` ${logObject.typeName}.constructor` - : logObject.methodName != null - ? ` ${logObject.typeName}.${logObject.methodName}` - : logObject.functionName != null - ? ` ${logObject.functionName}` - : "" - : ""; - - let fileLocation: string = ""; - if ( - this.settings.displayFilePath === "displayAll" || - (this.settings.displayFilePath === "hideNodeModulesOnly" && - logObject.filePath.indexOf("node_modules") < 0) - ) { - fileLocation = `${logObject.filePath}:${logObject.lineNumber}`; - } - const concatenatedMetaLine: string = [name, fileLocation, functionName] - .join(" ") - .replace(/\s\s+/g, " ") - .trim(); - if (concatenatedMetaLine.length > 0) { - std.write( - LoggerHelper.styleString( - ["gray"], - `[${concatenatedMetaLine}]`, - this.settings.colorizePrettyLogs - ) + " \t" - ); - - if (this.settings.printLogMessageInNewLine === false) { - std.write(" \t"); - } else { - std.write("\n"); - } - } - - logObject.argumentsArray.forEach((argument: unknown | IErrorObject) => { - const typeStr: string = - this.settings.displayTypes === true - ? LoggerHelper.styleString( - ["grey", "bold"], - typeof argument + ":", - this.settings.colorizePrettyLogs - ) + " " - : ""; - - const errorObject: IErrorObject = argument as IErrorObject; - if (typeof argument === "object" && errorObject?.isError === true) { - this._printPrettyError(std, errorObject); - } else if ( - typeof argument === "object" && - errorObject?.isError !== true - ) { - std.write( - "\n" + - typeStr + - this._inspectAndHideSensitive( - argument, - this.settings.prettyInspectOptions - ) - ); - } else { - std.write(typeStr + this._formatAndHideSensitive(argument) + " "); - } - }); - std.write("\n"); - - if (logObject.stack != null) { - std.write( - LoggerHelper.styleString( - ["underline", "bold"], - "log stack:\n", - this.settings.colorizePrettyLogs - ) - ); - - this._printPrettyStack(std, logObject.stack); - } - } - - private _printPrettyError( - std: IStd, - errorObject: IErrorObject, - printStackTrace: boolean = true - ): void { - std.write( - "\n" + - LoggerHelper.styleString( - ["bgRed", "whiteBright", "bold"], - ` ${errorObject.name} `, - this.settings.colorizePrettyLogs - ) + - (errorObject.message != null - ? `\t${this._formatAndHideSensitive(errorObject.message)}` - : "") - ); - - if (Object.values(errorObject.details).length > 0) { - std.write( - LoggerHelper.styleString( - ["underline", "bold"], - "\ndetails:", - this.settings.colorizePrettyLogs - ) - ); - std.write( - "\n" + - this._inspectAndHideSensitive( - errorObject.details, - this.settings.prettyInspectOptions - ) - ); - } - - if (printStackTrace === true && errorObject?.stack?.length > 0) { - std.write( - LoggerHelper.styleString( - ["underline", "bold"], - "\nerror stack:", - this.settings.colorizePrettyLogs - ) - ); - - this._printPrettyStack(std, errorObject.stack); - } - if (errorObject.codeFrame != null) { - this._printPrettyCodeFrame(std, errorObject.codeFrame); - } - } - - private _printPrettyStack(std: IStd, stackObjectArray: IStackFrame[]): void { - std.write("\n"); - Object.values(stackObjectArray).forEach((stackObject: IStackFrame) => { - std.write( - LoggerHelper.styleString( - ["gray"], - "• ", - this.settings.colorizePrettyLogs - ) - ); - - if (stackObject.fileName != null) { - std.write( - LoggerHelper.styleString( - ["yellowBright"], - stackObject.fileName, - this.settings.colorizePrettyLogs - ) - ); - } - - if (stackObject.lineNumber != null) { - std.write( - LoggerHelper.styleString( - ["gray"], - ":", - this.settings.colorizePrettyLogs - ) - ); - std.write( - LoggerHelper.styleString( - ["yellow"], - stackObject.lineNumber, - this.settings.colorizePrettyLogs - ) - ); - } - - std.write( - LoggerHelper.styleString( - ["white"], - " " + (stackObject.functionName ?? ""), - this.settings.colorizePrettyLogs - ) - ); - - if ( - stackObject.filePath != null && - stackObject.lineNumber != null && - stackObject.columnNumber != null - ) { - std.write("\n "); - std.write( - fileNormalize( - LoggerHelper.styleString( - ["gray"], - `${stackObject.filePath}:${stackObject.lineNumber}:${stackObject.columnNumber}`, - this.settings.colorizePrettyLogs - ) - ) - ); - } - std.write("\n\n"); - }); - } - - private _printPrettyCodeFrame(std: IStd, codeFrame: ICodeFrame): void { - std.write( - LoggerHelper.styleString( - ["underline", "bold"], - "code frame:\n", - this.settings.colorizePrettyLogs - ) - ); - - let lineNumber: number = codeFrame.firstLineNumber; - codeFrame.linesBefore.forEach((line: string) => { - std.write(` ${LoggerHelper.lineNumberTo3Char(lineNumber)} | ${line}\n`); - lineNumber++; - }); - - std.write( - LoggerHelper.styleString(["red"], ">", this.settings.colorizePrettyLogs) + - " " + - LoggerHelper.styleString( - ["bgRed", "whiteBright"], - LoggerHelper.lineNumberTo3Char(lineNumber), - this.settings.colorizePrettyLogs - ) + - " | " + - LoggerHelper.styleString( - ["yellow"], - codeFrame.relevantLine, - this.settings.colorizePrettyLogs - ) + - "\n" - ); - lineNumber++; - - if (codeFrame.columnNumber != null) { - const positionMarker: string = - new Array(codeFrame.columnNumber + 8).join(" ") + `^`; - std.write( - LoggerHelper.styleString( - ["red"], - positionMarker, - this.settings.colorizePrettyLogs - ) + "\n" - ); - } - - codeFrame.linesAfter.forEach((line: string) => { - std.write(` ${LoggerHelper.lineNumberTo3Char(lineNumber)} | ${line}\n`); - lineNumber++; - }); - } - - private _logObjectToJson(logObject: ILogObject): ILogObjectStringifiable { - return { - ...logObject, - argumentsArray: logObject.argumentsArray.map( - (argument: unknown | IErrorObject) => { - const errorObject: IErrorObject = argument as IErrorObject; - if (typeof argument === "object" && errorObject.isError) { - return { - ...errorObject, - nativeError: undefined, - errorString: this._formatAndHideSensitive( - errorObject.nativeError - ), - } as IErrorObjectStringifiable; - } else if (typeof argument === "object") { - return this._inspectAndHideSensitive( - argument, - this.settings.jsonInspectOptions - ); - } else { - return this._formatAndHideSensitive(argument); - } - } - ), - }; - } - - private _printJsonLog(std: IStd, logObject: ILogObject): void { - std.write(JSON.stringify(logObject) + "\n"); - } - - private _inspectAndHideSensitive( - object: object | null, - options: InspectOptions - ): string { - const maskedObject = this._maskValuesOfKeys(object); - return this._maskAny(inspect(maskedObject, options)); - } - - private _formatAndHideSensitive( - formatParam: unknown, - ...param: unknown[] - ): string { - return this._maskAny(format(formatParam, ...param)); - } - - private _maskValuesOfKeys(object: object | null) { - return LoggerHelper.logObjectMaskValuesOfKeys( - object, - this.settings.maskValuesOfKeys, - this.settings.maskPlaceholder - ); - } +} from './interfaces'; - private _maskAny(str: string) { - const formattedStr = str; +export { Logger } from './Logger'; +export { LoggerWithoutCallSite } from './LoggerWithoutCallSite'; - return this._maskAnyRegExp != null - ? formattedStr.replace(this._maskAnyRegExp, this.settings.maskPlaceholder) - : formattedStr; - } -} diff --git a/tests/error.test.ts b/tests/error.test.ts index 70c9abd8..5d0b08e2 100644 --- a/tests/error.test.ts +++ b/tests/error.test.ts @@ -1,5 +1,5 @@ import "ts-jest"; -import { IErrorObject, ILogObject, Logger } from "../src"; +import { IErrorObject, ILogObject, Logger, LoggerWithoutCallSite } from "../src"; import { doesLogContain } from "./helper"; let stdOut: string[] = []; @@ -20,6 +20,7 @@ const loggerConfig = { const loggerPretty: Logger = new Logger({ ...loggerConfig, type: "pretty" }); const loggerJson: Logger = new Logger({ ...loggerConfig, type: "json" }); +const loggerJsonWithoutCallsite: Logger = new LoggerWithoutCallSite({ ...loggerConfig, type: "json" }); class TestError extends Error { constructor(message: string) { @@ -65,6 +66,23 @@ describe("Logger: Error with details", () => { } }); + test("JSON: Check if logger works without callsite wrapper", (): void => { + try { + let obj: any; + const id = obj.id; // generating uncaught exception + } catch (err) { + loggerJsonWithoutCallsite.error(err); + const logObj: ILogObject = JSON.parse(stdErr[0]); + const errorObj: IErrorObject = logObj.argumentsArray?.[0] as IErrorObject; + + expect(errorObj?.message).toContain( + "Cannot read property 'id' of undefined" + ); + + expect(errorObj?.stack?.[0].fileName).toContain("error.test.ts"); + } + }); + test("Helper: Print error", (): void => { const error = new TestError("TestError"); const errorObject = loggerJson.prettyError(error, true); diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 00000000..53692fe9 --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm" + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ce8baafa..c73fba22 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,12 +15,10 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, - "declaration": true, - "declarationMap": true, "strict": true, "suppressImplicitAnyIndexErrors": true, "allowSyntheticDefaultImports": true, - "outDir": "dist" + "outDir": "dist/cjs" }, "include": [ "src/*.ts", diff --git a/tsconfig.types.json b/tsconfig.types.json new file mode 100644 index 00000000..9a4fe697 --- /dev/null +++ b/tsconfig.types.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "target": "es2015", + "removeComments": false, + "declaration": true, + "declarationMap": true, + "declarationDir": "dist/types", + "emitDeclarationOnly": true + } +} \ No newline at end of file