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