Skip to content

Commit b341f1f

Browse files
authored
Merge pull request #118 from aserto-dev/logger
add a event based logging mechanism
2 parents d021825 + cc88e60 commit b341f1f

12 files changed

Lines changed: 343 additions & 52 deletions

File tree

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,47 @@ const json = toJson(GetObjectsResponseSchema, objects)
818818
```
819819
820820
821+
## Custom Logging
822+
aserto-node publishes log events using the Node.js [Event emitter](https://nodejs.org/en/learn/asynchronous-work/the-nodejs-event-emitter#the-nodejs-event-emitter).
823+
The events for each log level are defined as:
824+
```ts
825+
export enum LOG_EVENT {
826+
DEBUG = "aserto-node-debug",
827+
ERROR = "aserto-node-error",
828+
INFO = "aserto-node-info",
829+
TRACE = "aserto-node-trace",
830+
WARN = "aserto-node-warn",
831+
}
832+
```
833+
Consumers can register a function when any of these events are triggered and handle the logging.
834+
```ts
835+
import { LOG_EVENT, setLogEventEmitter } from '@aserto/aserto-node'
836+
837+
// create a new Event emitter
838+
const emitter = new EventEmitter()
839+
840+
// configure aserto-node to use the emitter
841+
setLogEventEmitter(emitter)
842+
843+
// handle aserto-node log events
844+
emitter.on(LOG_EVENT.TRACE, (message) => {
845+
log.trace(message)
846+
})
847+
emitter.on(LOG_EVENT.DEBUG, (message) => {
848+
log.debug(message)
849+
})
850+
851+
emitter.on(LOG_EVENT.INFO, (message) => {
852+
log.info(message)
853+
})
854+
emitter.on(LOG_EVENT.WARN, (message) => {
855+
log.warn(message)
856+
})
857+
emitter.on(LOG_EVENT.ERROR, (message) => {
858+
log.error(message)
859+
})
860+
```
861+
821862
822863
## Deprecated Methods
823864

__tests__/integration/index.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { EventEmitter } from "events";
12
import express, { Express, Request, Response } from "express";
23
import nJwt from "njwt";
34
import { describe } from "node:test";
5+
import pino from "pino";
46
import request from "supertest";
57

68
import {
@@ -15,12 +17,14 @@ import {
1517
HEADER_ASERTO_MANIFEST_REQUEST,
1618
ImportMsgCase,
1719
ImportOpCode,
20+
LOG_EVENT,
1821
MANIFEST_REQUEST_DEFAULT,
1922
NotFoundError,
2023
policyContext,
2124
policyInstance,
2225
readAsyncIterable,
2326
serializeAsyncIterable,
27+
setLogEventEmitter,
2428
} from "../../lib";
2529
import { Topaz, TOPAZ_TIMEOUT } from "../topaz";
2630

@@ -1716,6 +1720,59 @@ types:
17161720
}),
17171721
).rejects.toThrow(NotFoundError);
17181722
});
1723+
1724+
describe("logging", () => {
1725+
beforeAll(() => {
1726+
process.env.NODE_TRACE_MESSAGE = "true";
1727+
const log = pino(
1728+
{
1729+
name: "aserto-node",
1730+
timestamp: pino.stdTimeFunctions.isoTime,
1731+
level: "debug",
1732+
formatters: {
1733+
level: (label) => {
1734+
return { level: label };
1735+
},
1736+
},
1737+
},
1738+
pino.multistream(
1739+
[
1740+
{ level: "trace", stream: process.stdout },
1741+
{ level: "debug", stream: process.stdout },
1742+
{ level: "info", stream: process.stdout },
1743+
{ level: "warn", stream: process.stdout },
1744+
{ level: "error", stream: process.stderr },
1745+
{ level: "fatal", stream: process.stderr },
1746+
],
1747+
{
1748+
levels: pino.levels.values,
1749+
dedupe: true,
1750+
},
1751+
),
1752+
);
1753+
const eventEmitter = new EventEmitter();
1754+
setLogEventEmitter(eventEmitter);
1755+
1756+
eventEmitter.on(LOG_EVENT.DEBUG, (msg) => {
1757+
log.debug(msg);
1758+
});
1759+
});
1760+
1761+
afterAll(() => {
1762+
process.env.NODE_TRACE_MESSAGE = undefined;
1763+
setLogEventEmitter(undefined);
1764+
});
1765+
1766+
it("allows a custom logger", async () => {
1767+
const config = {
1768+
url: "localhost:9292",
1769+
caFile: await topaz.caCert(),
1770+
};
1771+
1772+
const directory = DirectoryServiceV3(config);
1773+
directory.objects({ objectType: "user" });
1774+
});
1775+
});
17191776
});
17201777

17211778
describe("Authorizer", () => {

__tests__/topaz.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from "fs";
22
import util from "node:util";
33

44
import { DirectoryServiceV3 } from "../lib";
5-
import { log } from "../lib/log";
5+
import { logger } from "../lib/log";
66

77
// eslint-disable-next-line @typescript-eslint/no-require-imports
88
const exec = util.promisify(require("node:child_process").exec);
@@ -17,11 +17,11 @@ export class Topaz {
1717
const certsDir = await this.caCert();
1818

1919
await retry(async () => fs.readFileSync(certsDir), RETRY_OPTIONS);
20-
log("certificates are ready");
20+
logger.debug("certificates are ready");
2121

2222
await execute("topaz config info");
2323

24-
log(`topaz start with ${certsDir}`);
24+
logger.debug(`topaz start with ${certsDir}`);
2525

2626
const directoryClient = DirectoryServiceV3({
2727
url: "localhost:9292",
@@ -63,11 +63,11 @@ const retry = async <T>(
6363
try {
6464
return await fn();
6565
} catch (error) {
66-
log((error as Error).message);
66+
logger.debug((error as Error).message);
6767
if (retries <= 0) {
6868
throw error;
6969
}
70-
log(`Retrying...`);
70+
logger.debug(`Retrying...`);
7171
await sleep(retryIntervalMs);
7272
return retry(fn, { retries: retries - 1, retryIntervalMs });
7373
}
@@ -77,11 +77,11 @@ const sleep = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
7777
const execute = async (command: string) => {
7878
const { error, stdout, stderr } = await exec(command);
7979
if (error) {
80-
log(`error: ${error.message}`);
80+
logger.debug(`error: ${error.message}`);
8181
return;
8282
}
8383
if (stderr) {
84-
log(`stderr: ${stderr}`);
84+
logger.debug(`stderr: ${stderr}`);
8585
return;
8686
}
8787
return stdout;

lib/directory/v3/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
import { createGrpcTransport } from "@connectrpc/connect-node";
4848
import { createAsyncIterable as createAsyncIterable$ } from "@connectrpc/connect/protocol";
4949

50-
import { log } from "../../log";
50+
import { logger } from "../../log";
5151
import {
5252
handleError,
5353
setCustomHeaders,
@@ -651,7 +651,7 @@ export class DirectoryV3 {
651651
* @param {T[]} items - The array of items to iterate over.
652652
*/
653653
export async function* createAsyncIterable<T>(items: T[]) {
654-
log("[Deprecated]: please use `createImportRequest`");
654+
logger.warn("[Deprecated]: please use `createImportRequest`");
655655
yield* createImportRequest(items as ImportRequest$[]);
656656
}
657657

@@ -678,7 +678,7 @@ export async function* createImportRequest(params: ImportRequest$[]) {
678678
* @returns The converted Protobuf Struct.
679679
*/
680680
export function objectPropertiesAsStruct(value: JsonObject): JsonObject {
681-
log(
681+
logger.warn(
682682
"[Deprecated]: This version of SDK does not require conversion from JSON to Struct. Use the value directly",
683683
);
684684
return value;

lib/errorHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextFunction, Response } from "express";
22

3-
import { log } from "./log";
3+
import { logger } from "./log";
44

55
const errorHandler =
66
(next: NextFunction, failWithError: boolean) =>
@@ -13,7 +13,7 @@ const errorHandler =
1313
message: `aserto-node: ${err_message}`,
1414
});
1515
}
16-
log(err_message, "ERROR");
16+
logger.error(err_message);
1717
res.append(
1818
"WWW-Authenticate",
1919
`Bearer error="${encodeURIComponent(err_message)}"`,

lib/identityContext.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { jwtDecode } from "jwt-decode";
55
import { IdentityType } from "@aserto/node-authorizer/src/gen/cjs/aserto/authorizer/v2/api/identity_context_pb";
66

77
import identityContext from "./authorizer/model/identityContext";
8-
import { log } from "./log";
8+
import { logger } from "./log";
99

1010
export interface IdentityContextOptions {
1111
useAuthorizationHeader: boolean;
@@ -36,11 +36,11 @@ export default (req: express.Request, options: IdentityContextOptions) => {
3636
: "";
3737

3838
type = "JWT";
39-
40-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
4139
} catch (error) {
42-
// TODO: resolve error type ${error.message}
43-
log(`Authorization header contained malformed JWT:`, "ERROR");
40+
logger.error(
41+
`Authorization header contained malformed JWT: ${(error as Error).message}`,
42+
);
43+
4444
type = "NONE";
4545
}
4646
} else {

lib/index.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Opcode } from "@aserto/node-directory/src/gen/cjs/aserto/directory/importer/v3/importer_pb";
2+
import { JsonValue } from "@bufbuild/protobuf";
23

34
import { Authorizer, authz } from "./authorizer";
45
import AnonymousIdentityMapper from "./authorizer/mapper/identity/anonymous";
@@ -31,6 +32,56 @@ import { CustomHeaders, DirectoryV3Config } from "./directory/v3/types";
3132
import { displayStateMap } from "./displayStateMap";
3233
import { is } from "./is";
3334
import { AuthzOptions, jwtAuthz } from "./jwtAuthz";
35+
import {
36+
getLogEventEmitter,
37+
LOG_EVENT,
38+
LOG_LEVELS,
39+
setLogEventEmitter,
40+
} from "./log";
41+
42+
type LogLevel = keyof typeof LOG_LEVELS;
43+
const currentLevel = LOG_LEVELS[(process.env.LOG_LEVEL || "INFO") as LogLevel];
44+
45+
const log = (message: JsonValue, level: number = LOG_LEVELS.INFO) => {
46+
const timestamp = new Date().toISOString();
47+
if (process.env.NODE_TRACE) {
48+
// eslint-disable-next-line no-console
49+
console.trace(`${timestamp} ${level}: ${JSON.stringify(message)}`);
50+
} else {
51+
if (level === LOG_LEVELS.ERROR) {
52+
// eslint-disable-next-line no-console
53+
console.error(`${timestamp} ${level}: ${JSON.stringify(message)}`);
54+
} else if (level >= currentLevel) {
55+
// eslint-disable-next-line no-console
56+
console.log(
57+
`${timestamp} ${level}: aserto-node: ${JSON.stringify(message)}`,
58+
);
59+
}
60+
}
61+
};
62+
63+
const logEventEmitter = getLogEventEmitter();
64+
65+
logEventEmitter.on(LOG_EVENT.DEBUG, (message) => {
66+
log(message, LOG_LEVELS.DEBUG);
67+
});
68+
69+
logEventEmitter.on(LOG_EVENT.ERROR, (message) => {
70+
log(message, LOG_LEVELS.ERROR);
71+
});
72+
73+
logEventEmitter.on(LOG_EVENT.INFO, (message) => {
74+
log(message, LOG_LEVELS.INFO);
75+
});
76+
77+
logEventEmitter.on(LOG_EVENT.TRACE, (message) => {
78+
log(message, LOG_LEVELS.TRACE);
79+
});
80+
81+
logEventEmitter.on(LOG_EVENT.WARN, (message) => {
82+
log(message, LOG_LEVELS.WARN);
83+
});
84+
3485
export {
3586
AnonymousIdentityMapper,
3687
Authorizer,
@@ -51,6 +102,8 @@ export {
51102
is,
52103
jwtAuthz,
53104
JWTIdentityMapper,
105+
LOG_EVENT,
106+
logEventEmitter,
54107
MANIFEST_REQUEST_DEFAULT,
55108
MANIFEST_REQUEST_METADATA_ONLY,
56109
MANIFEST_REQUEST_MODEL_ONLY,
@@ -65,6 +118,7 @@ export {
65118
queryOptions,
66119
readAsyncIterable,
67120
serializeAsyncIterable,
121+
setLogEventEmitter,
68122
SubIdentityMapper,
69123
};
70124

0 commit comments

Comments
 (0)