Skip to content

Commit

Permalink
Create logger (#674)
Browse files Browse the repository at this point in the history
Co-authored-by: Douglas Gubert <[email protected]>
  • Loading branch information
tapiarafael and d-gubert authored Dec 14, 2023
1 parent 1e36587 commit 1e4839b
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 31 deletions.
6 changes: 4 additions & 2 deletions deno-runtime/AppObjectRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export type Maybe<T> = T | null | undefined;

export const AppObjectRegistry = new class {
registry: Record<string, unknown> = {};

public get(key: string): unknown {
return this.registry[key];
public get<T>(key: string): Maybe<T> {
return this.registry[key] as Maybe<T>;
}

public set(key: string, value: unknown): void {
Expand Down
5 changes: 5 additions & 0 deletions deno-runtime/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

134 changes: 134 additions & 0 deletions deno-runtime/lib/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import * as stackTrace from 'npm:stack-trace'
import { StackFrame } from 'npm:stack-trace'

enum LogMessageSeverity {
DEBUG = 'debug',
INFORMATION = 'info',
LOG = 'log',
WARNING = 'warning',
ERROR = 'error',
SUCCESS = 'success',
}

type Entry = {
caller: string;
severity: LogMessageSeverity;
method: string;
timestamp: Date;
args: Array<unknown>;
}

interface ILoggerStorageEntry {
appId: string;
method: string;
entries: Array<Entry>;
startTime: Date;
endTime: Date;
totalTime: number;
_createdAt: Date;
}

export class Logger {
private appId: string;
private entries: Array<Entry>;
private start: Date;
private method: string;

constructor(method: string, appId: string) {
this.appId = appId;
this.method = method;
this.entries = [];
this.start = new Date();
}

public debug(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.DEBUG, this.getStack(stackTrace.get()), ...args)
}

public info(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.INFORMATION, this.getStack(stackTrace.get()), ...args)
}

public log(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.LOG, this.getStack(stackTrace.get()), ...args)
}

public warning(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.WARNING, this.getStack(stackTrace.get()), ...args)
}

public error(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.ERROR, this.getStack(stackTrace.get()), ...args)
}

public success(...args: Array<unknown>): void {
this.addEntry(LogMessageSeverity.SUCCESS, this.getStack(stackTrace.get()), ...args)
}

private addEntry(severity: LogMessageSeverity, caller: string, ...items: Array<unknown>): void {
const i = items.map((args) => {
if (args instanceof Error) {
return JSON.stringify(args, Object.getOwnPropertyNames(args));
}
if (typeof args === 'object' && args !== null && 'stack' in args) {
return JSON.stringify(args, Object.getOwnPropertyNames(args));
}
if (typeof args === 'object' && args !== null && 'message' in args) {
return JSON.stringify(args, Object.getOwnPropertyNames(args));
}
const str = JSON.stringify(args, null, 2);
return str ? JSON.parse(str) : str; // force call toJSON to prevent circular references

});

this.entries.push({
caller,
severity,
method: this.method,
timestamp: new Date(),
args: i,
});
}

private getStack(stack: Array<StackFrame>): string {
let func = 'anonymous';

if (stack.length === 1) {
return func;
}

const frame = stack[1];

if (frame.getMethodName() === null) {
func = 'anonymous OR constructor';
} else {
func = frame.getMethodName();
}

if (frame.getFunctionName() !== null) {
func = `${func} -> ${frame.getFunctionName()}`;
}

return func;
}

private getTotalTime(): number {
return new Date().getTime() - this.start.getTime();
}

public hasEntries(): boolean {
return this.entries.length > 0;
}

public getLogs(): ILoggerStorageEntry {
return {
appId: this.appId,
method: this.method,
entries: this.entries,
startTime: this.start,
endTime: new Date(),
totalTime: this.getTotalTime(),
_createdAt: new Date(),
};
}
}
71 changes: 56 additions & 15 deletions deno-runtime/lib/messenger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as jsonrpc from 'jsonrpc-lite';

import { AppObjectRegistry } from "../AppObjectRegistry.ts";
import type { Logger } from './logger.ts'

export type RequestDescriptor = Pick<jsonrpc.RequestObject, 'method' | 'params'>;

export type NotificationDescriptor = Pick<jsonrpc.NotificationObject, 'method' | 'params'>;
Expand All @@ -26,6 +29,36 @@ export function isErrorResponse(message: jsonrpc.JsonRpc): message is jsonrpc.Er
const encoder = new TextEncoder();
export const RPCResponseObserver = new EventTarget();

export const Transport = new class Transporter {
private selectedTransport: Transporter["stdoutTransport"] | Transporter["noopTransport"];

constructor() {
this.selectedTransport = this.stdoutTransport.bind(this);
}

private async stdoutTransport(message: jsonrpc.JsonRpc): Promise<void> {
const encoded = encoder.encode(message.serialize());
await Deno.stdout.write(encoded);
}

private async noopTransport(_message: jsonrpc.JsonRpc): Promise<void> { }

public selectTransport(transport: 'stdout' | 'noop'): void {
switch (transport) {
case 'stdout':
this.selectedTransport = this.stdoutTransport.bind(this);
break;
case 'noop':
this.selectedTransport = this.noopTransport.bind(this);
break;
}
}

public send(message: jsonrpc.JsonRpc): Promise<void> {
return this.selectedTransport(message);
}
}

export function parseMessage(message: string) {
const parsed = jsonrpc.parse(message);

Expand All @@ -40,51 +73,59 @@ export function parseMessage(message: string) {
return parsed;
}

export async function send(message: jsonrpc.JsonRpc): Promise<void> {
const encoded = encoder.encode(message.serialize());
await Deno.stdout.write(encoded);
}

export async function sendInvalidRequestError(): Promise<void> {
const rpc = jsonrpc.error(null, jsonrpc.JsonRpcError.invalidRequest(null));

await send(rpc);
await Transport.send(rpc);
}

export async function sendInvalidParamsError(id: jsonrpc.ID): Promise<void> {
const rpc = jsonrpc.error(id, jsonrpc.JsonRpcError.invalidParams(null));

await send(rpc);
await Transport.send(rpc);
}

export async function sendParseError(): Promise<void> {
const rpc = jsonrpc.error(null, jsonrpc.JsonRpcError.parseError(null));

await send(rpc);
await Transport.send(rpc);
}

export async function sendMethodNotFound(id: jsonrpc.ID): Promise<void> {
const rpc = jsonrpc.error(id, jsonrpc.JsonRpcError.methodNotFound(null));

await send(rpc);
await Transport.send(rpc);
}

export async function errorResponse({ error: { message, code = -32000, data }, id }: ErrorResponseDescriptor): Promise<void> {
export async function errorResponse({ error: { message, code = -32000, data = {} }, id }: ErrorResponseDescriptor): Promise<void> {
const logger = AppObjectRegistry.get<Logger>('logger');

if (logger?.hasEntries()) {
data.logs = logger.getLogs();
}

const rpc = jsonrpc.error(id, new jsonrpc.JsonRpcError(message, code, data));

await send(rpc);
await Transport.send(rpc);
}

export async function successResponse({ id, result }: SuccessResponseDescriptor): Promise<void> {
const rpc = jsonrpc.success(id, result);
const payload = { value: result } as Record<string, unknown>;
const logger = AppObjectRegistry.get<Logger>('logger');

if (logger?.hasEntries()) {
payload.logs = logger.getLogs();
}

const rpc = jsonrpc.success(id, payload);

await send(rpc);
await Transport.send(rpc);
}

export async function sendRequest(requestDescriptor: RequestDescriptor): Promise<jsonrpc.SuccessObject> {
const request = jsonrpc.request(Math.random().toString(36).slice(2), requestDescriptor.method, requestDescriptor.params);

await send(request);
await Transport.send(request);

// TODO: add timeout to this
return new Promise((resolve, reject) => {
Expand All @@ -107,5 +148,5 @@ export async function sendRequest(requestDescriptor: RequestDescriptor): Promise
export function sendNotification({ method, params }: NotificationDescriptor) {
const request = jsonrpc.notification(method, params);

send(request);
Transport.send(request);
}
Loading

0 comments on commit 1e4839b

Please sign in to comment.