Skip to content

Commit

Permalink
feat: adds support for generating machine readable logs (#124)
Browse files Browse the repository at this point in the history
* feat: adds support for generating machine readable logs

Adds support for generating machine readable logs as JSON objects through the Adze configuration.
Also resolves some underlying problems with the timeNow modifier where it was being applied only
when a label was assigned to a log. timeNow no longer requires a label.
  • Loading branch information
AJStacy authored May 5, 2022
1 parent a9be92e commit e655051
Show file tree
Hide file tree
Showing 32 changed files with 1,427 additions and 90 deletions.
111 changes: 81 additions & 30 deletions demo_funcs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,31 @@ export default function runDemo(lib, el) {
namespaceExcludeFilter(lib);
labelIncludeFilter(lib);
labelExcludeFilter(lib);
machineReadableLogs(lib);
}

function screenshots({ adze, createShed }, el) { }

function screenshotDemo({ adze }) {
const log = adze();
log.alert('Example alert log');
log.error('Example error log');
log.warn('Example warning log');
log.info('Example info log');
log.fail('Example fail log');
log.success('Example success log');
log.log('Example log');
log.debug('Example debug log');
log.verbose('Example verbose log');
const log2 = adze({ useEmoji: true });
log2.alert('Example alert log');
log2.error('Example error log');
log2.warn('Example warning log');
log2.info('Example info log');
log2.fail('Example fail log');
log2.success('Example success log');
log2.log('Example log');
log2.debug('Example debug log');
log2.verbose('Example verbose log');
adze().alert('Example alert log');
adze().error('Example error log');
adze().warn('Example warning log');
adze().info('Example info log');
adze().fail('Example fail log');
adze().success('Example success log');
adze().log('Example log');
adze().debug('Example debug log');
adze().verbose('Example verbose log');
const log2 = adze({ useEmoji: true }).seal();
log2().alert('Example alert log');
log2().error('Example error log');
log2().warn('Example warning log');
log2().info('Example info log');
log2().fail('Example fail log');
log2().success('Example success log');
log2().log('Example log');
log2().debug('Example debug log');
log2().verbose('Example verbose log');
adze({
useEmoji: true,
customLevels: {
Expand Down Expand Up @@ -83,16 +83,16 @@ function defaultLevels({ adze }) {

function defaultLevelsWithEmoji({ adze }) {
console.log('\n----- Default Levels w/ Emoji -----\n');
const log = adze({ useEmoji: true });
log.alert('This is an alert!');
log.error('This is an error!');
log.warn('This is a warn!');
log.info('This is an info!');
log.fail('This is a failure!');
log.success('This is a success!');
log.log('This is a log!');
log.debug('This is a debug!');
log.verbose('This is a verbose!');
const log = adze({ useEmoji: true }).seal();
log().alert('This is an alert!');
log().error('This is an error!');
log().warn('This is a warn!');
log().info('This is an info!');
log().fail('This is a failure!');
log().success('This is a success!');
log().log('This is a log!');
log().debug('This is a debug!');
log().verbose('This is a verbose!');
}

function defaultLevelsWithGlobalOverride({ adze, createShed, removeShed }) {
Expand Down Expand Up @@ -452,4 +452,55 @@ function labelExcludeFilter({ adze }) {
logger().label('foo').fail("I should not print.");
logger().label('bar').success("I should print.");
logger().success("I should print.");
}

function machineReadableLogs({ adze, createShed, removeShed }) {
removeShed();
createShed();
const cfg = { machineReadable: true, meta: { hello: 'world' } };
const log = adze(cfg).seal();
console.log('\n----- Machine Readable Logs Demo -----\n');

log().alert('Machine-readable alert');
log().error('Machine-readable error');
log().warn('Machine-readable warning');
log().info('Machine-readable info');
log().fail('Machine-readable fail');
log().success('Machine-readable success');
log().log('Machine-readable');
log().debug('Machine-readable debug');
log().verbose('Machine-readable verbose');

log().label('test').log('A log with a label.');
log().ns('foo', 'bar').log('A log with namespaces.');
log().timestamp.log('A log with a timestamp.');
log().label('timer').time.log('Starting a timer.');
log().label('timer').timeEnd.log('Ending a timer.');
log().label('test').timeNow.log('Time ellapsed since load.');
log().meta('foo', 'bar').meta('bar', 'baz').log('Adding foo bar meta data.');
log().label('counting').count.log('Counting this log.');
log().label('counting').count.log('Counting this log.');
log().label('counting').count.log('Counting this log.');

log().group.log('Opening a log group.');
log().log('testing');
log().groupEnd.log();

log().groupCollapsed.log('Opening a log group collapsed.');
log().log('testing');
log().groupEnd.log();

log().label('my-thread').thread('foo', 'bar');
log().label('my-thread').thread('bar', 'baz');
log().label('my-thread').dump.log('Dumping the MDC context.');

log().trace.log('This log has a stacktrace.');
log().dir.log('A dir log');
log().dirxml.log('A dirxml log');
log().table.log([
{ firstName: 'Andrew', lastName: 'Stacy' },
{ firstName: 'Jim', lastName: 'Bob' },
]);

adze({ machineReadable: true, captureStacktrace: true }).log('Testing the global captureStacktrace configuration.');
}
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ module.exports = {
'modifiers',
'default-terminators',
'other-terminators',
'machine-readable-logs',
'filtering-and-utility-functions',
'getters-and-setters',
'data',
Expand Down
2 changes: 2 additions & 0 deletions docs/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface Configuration {
unstyled?: boolean;
terminalColorFidelity?: 0 | 1 | 2 | 3;
captureStacktrace?: boolean;
machineReadable?: boolean;
baseStyle?: string;
logLevels?: LogLevels;
customLevels?: Partial<LogLevels>;
Expand Down Expand Up @@ -54,6 +55,7 @@ type ConsoleMethod =
| unstyled | false | Disables all styling of logs. Useful for stdout use cases. |
| terminalColorFidelity | 1 | Control terminal color fidelity with [Chalk](https://github.com/chalk/chalk#chalklevel). |
| captureStacktrace | false | Logs will record their stacktrace when they are created. Disabled by default for performance. |
| machineReadable | false | When enabled all logs will be generated as [machine readable JSON objects](../guide/machine-readable-logs.md). |
| baseStyle | [Reference](#styling) | These styles will be applied to all default log levels. |
| logLevels | [Reference](#log-levels-log-level-definition) | Configuration for default Adze log levels. |
| customLevels | [Reference](#log-levels-log-level-definition) | Configuration for custom Adze log levels. |
Expand Down
1 change: 0 additions & 1 deletion docs/guide/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ A label data object is generated from a label instance. This object contains dat
```typescript
interface LabelData {
name: string | null;
timeNow: string | null;
timeEllapsed: string | null;
count: number | null;
}
Expand Down
35 changes: 35 additions & 0 deletions docs/guide/machine-readable-logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Machine Readable Logs

When working with your logs, typically in a production environment, the best practice is to generate logs as JSON objects. By generating them as JSON objects it makes them simpler to index, search, and sort at a later time. Adze supports this out of the box through the [`machineReadable` configuration property](#configuration). When this is enabled, all logs will cease printing in a user-friendly human-readable format and print to the console as JSON objects. These objects will contain all of the information that would normally be present in a human-readable log.

## Configuration

To enable machine readable JSON logs you must activate it through Adze configuration.

```javascript
import adze from 'adze';

adze({ machineReadable: true }).log('This log will be generated as JSON.');
```

## Common Usage

The example above for configuring a machine readable log would only cause that single log that is defined to be machine readable. Not only that, but this log would also always be machine readable. This obviously is not the behavior we are after, so how can we set up our application to become machine readable in production environments? Let's look at an example using node environment variables:

```javascript
import adze from 'adze';

// When process.env.production is set to "true", we will
// set the machineReadable property to true.
const cfg = {
machineReadable: process.env.environment === 'production'
};

// We'll seal our configuration into a new log factory named logger
const logger = adze(cfg).seal();

// Now we'll export our new logger for use throughout our application.
export default logger;
```

Now, as we import and use our logger throughout our application, when our environment is not 'production' we will have human readable logs. When our environment is production all logs will now be generated as machine readable JSON.
2 changes: 0 additions & 2 deletions docs/guide/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -761,8 +761,6 @@ adze({ useEmoji: true }).label('loop').timeEnd.log('Performance of our loop.');

This modifier logs the time ellapsed since the page has loaded. This is useful for measuring page load performance rather than performance of a particular piece of code. This modifier is **not** dependent upon a [label](#label) or [Shed](./shed-concepts.md).

If a [label](#label) and a [Shed](./shed-concepts.md) exist, this modifier will record it's timestamp to the label.

_This is not a standard API._

### Interface
Expand Down
13 changes: 7 additions & 6 deletions src/_contracts/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@ export type ConsoleMethod =
| 'dirxml';

export interface Defaults {
logLevel: number;
useEmoji: boolean;
terminalColorFidelity: 0 | 1 | 2 | 3;
captureStacktrace: boolean;
unstyled: boolean;
baseStyle: string;
logLevels: LogLevels;
captureStacktrace: boolean;
customLevels: LogLevels;
filters: AdzeFilters;
logLevel: number;
logLevels: LogLevels;
machineReadable: boolean;
meta: {
[key: string]: unknown;
};
terminalColorFidelity: 0 | 1 | 2 | 3;
useEmoji: boolean;
unstyled: boolean;
}

export type LogLevels = {
Expand Down
21 changes: 21 additions & 0 deletions src/_contracts/Log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,24 @@ export interface TerminatedLog<C extends Constraints, I extends BaseLog<C>> {
export interface Constraints {
allowedNamespaces: string;
}

/**
* This interface describes the possible output when generating logs with
* machineReadable mode enabled.
*/
export interface JsonOutput {
method: ConsoleMethod;
level: number;
levelName: string;
args: any[];
timestamp: LogTimestamp;
groupAction?: 'open' | 'close';
label?: string;
namespace?: string[];
count?: number;
timeEllapsed?: string;
timeNow?: string;
context?: Record<string, unknown>;
stacktrace?: string;
meta?: Record<string, unknown>;
}
1 change: 0 additions & 1 deletion src/_contracts/Shed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface ShedUserConfig extends Partial<Omit<ShedConfig, 'globalCfg'>> {

export interface LabelData {
name: string | null;
timeNow: string | null;
timeEllapsed: string | null;
count: number | null;
}
1 change: 1 addition & 0 deletions src/_defaults/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const defaults: Defaults = {
baseStyle:
'font-size: 10px; font-weight: bold; border-radius: 0 10px 10px 0; border-width: 1px; border-style: solid;',
customLevels: {},
machineReadable: false,
meta: {},
logLevels: {
alert: {
Expand Down
26 changes: 0 additions & 26 deletions src/label/Label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ export class Label {
*/
private _count: number | null;

/**
* Time ellapsed since execution.
*/
private _timeNow: string | null;

/**
* Beginning of a timer for this label.
*/
Expand All @@ -39,24 +34,18 @@ export class Label {
name: string,
context: MetaData = {},
count: number | null = null,
timeNow: string | null = null,
timeEllapsed: string | null = null
) {
this.name = name;
this._context = context;
this._count = count;
this._timeNow = timeNow;
this._timeEllapsed = timeEllapsed;
}

public get count(): number | null {
return this._count;
}

public get timeNow(): string | null {
return this._timeNow;
}

public get timeEllapsed(): string | null {
return this._timeEllapsed;
}
Expand Down Expand Up @@ -118,13 +107,6 @@ export class Label {
this._timeStart = hrtime();
}

/**
* Sets the current execution time.
*/
public captureTimeNow(): void {
this._timeNow = Label.formatTime(hrtime());
}

/**
* Ends a previously started timer and records the
* time ellapsed.
Expand All @@ -137,13 +119,6 @@ export class Label {
}
}

/**
* Generates the current execution time.
*/
public static createTimeNow(): string {
return Label.formatTime(hrtime());
}

/**
* Takes an HrTime tuple and converts it into a human-readable formatted
* string in the format of `{sec}s {ms}ms`.
Expand All @@ -159,7 +134,6 @@ export class Label {
public get data(): LabelData {
return {
name: this.name,
timeNow: this._timeNow,
timeEllapsed: this._timeEllapsed,
count: this._count,
};
Expand Down
Loading

0 comments on commit e655051

Please sign in to comment.