4 Commits
2.1.0 ... 2.3.0

5 changed files with 38 additions and 33 deletions

View File

@@ -54,7 +54,7 @@ written to the appender. This requires the assumption of all logs being written
to a text-based destination, which is not always the case for us.
In this library, all logs are internally stored as structured data, defined by
the *LogMessage* interface. Notably, the *message* field can be either a string
the *LogMessage* interface. Notably, the *msg* field can be either a string
or an object, allowing for the addition of arbitrary fields to be added to the
log event.
@@ -65,9 +65,9 @@ messages written to the console. the *ApiAppender* would be an example of an
appender for which layout would be irrelevant.
Finally, notice that all of the appenders provided by this library
automatically persist log messages as structured data, flattening the `message`
automatically persist log messages as structured data, flattening the `msg`
field if it is an object. *ConsoleAppender* will write the log message as a
JSON object, promoting fields in the `message` object to top-level fields in
JSON object, promoting fields in the `msg` object to top-level fields in
the output. For example:
```typescript
@@ -87,24 +87,24 @@ someBusinessLogic();
results in the following two events logged as output to the console:
```json
{"timestamp":"2021-09-01T00:00:00.000Z","level":"INFO","scope":"example","message":"Starting application"}
{"timestamp":"2021-09-01T00:00:00.000Z","level":"INFO","scope":"example","msg":"Starting application"}
{"timestamp":"2021-09-01T00:00:00.000Z","level":"DEBUG","scope":"example","msg":"Doing some business logic","method":"someBusinessLogic","foo":"bar"}
```
Note that the field name in the structured data for string messages is
"message". In the case of an object message, the fields are used as provided.
"msg". In the case of an object message, the fields are used as provided.
Fields defined on the *LogMessage* interface are reserved and should not be
used as keys in the message object (and will be ignored if they are). So, for
used as keys in the msg object (and will be ignored if they are). So, for
example:
```typescript
logger.debug({ level: 'WARN', message: 'Example of ignored fields', timestamp: 'today' });
logger.debug({ level: 'WARN', msg: 'Example of ignored fields', timestamp: 'today' });
results in the following event logged as output to the console (note the
ignored `level` and `timestamp` fields from the object):
```json
{"timestamp":"2021-09-01T00:00:00.000Z","level":"DEBUG","scope":"example","message":"Example of ignored fields"}
{"timestamp":"2021-09-01T00:00:00.000Z","level":"DEBUG","scope":"example","msg":"Example of ignored fields"}
```
This flattening behavior is implemented in the `flattenMessage` function

View File

@@ -1,6 +1,6 @@
{
"name": "@jdbernard/logging",
"version": "2.1.0",
"version": "2.3.0",
"description": "Simple Javascript logging service.",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -39,6 +39,8 @@ export class ConsoleLogAppender implements LogAppender {
switch (msg.level) {
case LogLevel.ALL:
case LogLevel.TRACE:
logMethod = console.trace;
break;
case LogLevel.LOG:
logMethod = console.log;
break;
@@ -53,26 +55,26 @@ export class ConsoleLogAppender implements LogAppender {
break;
case LogLevel.ERROR:
case LogLevel.FATAL:
logMethod = console.trace;
logMethod = console.error;
break;
}
const fmtMsg = this.formatter(msg);
if (typeof fmtMsg === "string") {
if (msg.error || msg.stacktrace) {
logMethod(fmtMsg, msg.error ?? msg.stacktrace);
if (msg.err || msg.stacktrace) {
logMethod(fmtMsg, msg.err ?? msg.stacktrace);
} else {
logMethod(fmtMsg);
}
} else {
const { message, _error, _stacktrace, ...rest } = fmtMsg;
const { message, _err, _stacktrace, ...rest } = fmtMsg;
const summary = `${LogLevel[msg.level]} -- ${msg.scope}: ${
message ?? fmtMsg.method
}\n`;
if (msg.error || msg.stacktrace) {
logMethod(summary, msg.error ?? msg.stacktrace, rest);
if (msg.err || msg.stacktrace) {
logMethod(summary, msg.err ?? msg.stacktrace, rest);
} else {
logMethod(summary, rest);
}

View File

@@ -27,30 +27,33 @@ export interface LogMessage {
level: LogLevel;
msg: string | Record<string, unknown>;
stacktrace?: string;
error?: Error;
timestamp: Date;
err?: Error;
ts: Date;
}
export type FlattenedLogMessage = Record<string, unknown>;
/**
* Flatten a log message to a plain object. The *message* field can be either a
* string or an object. In the case of an object message, the LogMessage should
* be flattened before being emitted by an appender, promoting the object's
* fields to the top level of the message. Fields defined on the *LogMessage*
* interface are reserved and should not be used as keys in the message object
* (and will be ignored if they are).
* Flatten a log message to a plain object in preparation for emission to an
* appender.
*
* In general, the *message* field can be either a string or an object. In the
* case of an object message, the LogMessage should be flattened before being
* emitted by an appender, promoting the object's fields to the top level of
* the message. Fields defined on the *LogMessage* interface are reserved and
* should not be used as keys in the message object (and will be ignored if
* they are).
*
* So, for example:
*
* ```typescript
* const logger = logService.getLogger('example');
* logger.info({ foo: 'bar', baz: 'qux', timestamp: 'today', level: LogLevel.WARN });
* logger.info({ foo: 'bar', baz: 'qux', ts: 'today', level: LogLevel.WARN });
* ```
*
* Should result after flattening in a structured log message like:
* Will result in a structured log message after flattening like:
* ```json
* {"scope":"example","level":"INFO","foo":"bar","baz":"qux","timestamp":"2020-01-01T00:00:00.000Z"}
* {"scope":"example","level":"INFO","foo":"bar","baz":"qux","ts":"2020-01-01T00:00:00.000Z"}
* ```
*/
export function flattenMessage(logMsg: LogMessage): FlattenedLogMessage {
@@ -63,8 +66,8 @@ export function flattenMessage(logMsg: LogMessage): FlattenedLogMessage {
"scope",
"level",
"stacktrace",
"error",
"timestamp",
"err",
"ts",
]),
...rest,
level: LogLevel[logMsg.level],

View File

@@ -67,17 +67,17 @@ export class Logger {
level,
msg: '',
stacktrace: '',
timestamp: new Date(),
ts: new Date(),
};
if (msg === undefined || msg === null) {
logMsg.msg = msg;
logMsg.stacktrace = stacktrace ?? '';
} else if (msg instanceof Error) {
const error = msg as Error;
logMsg.error = error;
logMsg.msg = `${error.name}: ${error.message}`;
logMsg.stacktrace = stacktrace ?? error.stack ?? '';
const err = msg as Error;
logMsg.err = err;
logMsg.msg = `${err.name}: ${err.message}`;
logMsg.stacktrace = stacktrace ?? err.stack ?? '';
} else if (isDeferredMsg(msg)) {
logMsg.msg = msg();
logMsg.stacktrace = stacktrace == null ? '' : stacktrace;