Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create logger #674

Merged
merged 18 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading