Skip to content

Commit

Permalink
Avoid using Node API in edge package
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrHeinz committed Sep 11, 2023
1 parent 3d0ab5e commit c346166
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 381 deletions.
3 changes: 0 additions & 3 deletions packages/edge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@
"devDependencies": {
"@types/babel__core": "7.0.4",
"@types/babel__traverse": "7.0.4",
"@types/fetch-mock": "^7.3.1",
"cross-fetch": "^3.0.4",
"nock": "^10.0.6",
"npm-run-all": "^4.1.5",
"typescript": "^4.9.5"
},
Expand Down
36 changes: 1 addition & 35 deletions packages/edge/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Context, StackContextHint } from "@logtail/types";
import { dirname, relative } from "path";
import stackTrace, { StackFrame } from 'stack-trace';
import { Edge } from "./edge";

const mainFile = mainFileName();
/**
* Determines the file name and the line number from which the log
* was initiated (if we're able to tell).
Expand All @@ -17,17 +15,13 @@ export function getStackContext(logtail: Edge, stackContextHint?: StackContextHi
return {
context: {
runtime: {
file: relativeToMainModule(stackFrame.getFileName()),
file: stackFrame.getFileName(),
type: stackFrame.getTypeName(),
method: stackFrame.getMethodName(),
function: stackFrame.getFunctionName(),
line: stackFrame.getLineNumber(),
column: stackFrame.getColumnNumber(),
},
system: {
pid: process.pid,
main_file: mainFile,
}
}
};
}
Expand All @@ -53,31 +47,3 @@ function getRelevantStackFrame(frames: StackFrame[], stackContextHint?: StackCon

return frames[0];
}

function relativeToMainModule(fileName: string): string | null {
if (typeof(fileName) !== "string") {
return null;
} else if (fileName.startsWith("file:/")) {
const url = new URL(fileName);
return url.pathname;
} else {
const rootPath = dirname(mainFileName());
return relative(rootPath, fileName);
}
}

function mainFileName(): string {
let argv = process?.argv;
if (argv === undefined) return '';
// return first js file argument - arg ending in .js
for (const arg of argv) {
if (typeof(arg) !== "string" || arg.startsWith('-')) {
// break on first option
break;
}
if (arg.endsWith('.js')) {
return arg;
}
}
return '';
}
108 changes: 22 additions & 86 deletions packages/edge/src/edge.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import * as os from "os";
import * as path from "path";
import * as fs from "fs";
import { PassThrough, Writable } from "stream";

import nock from "nock";
import { ILogtailLog, LogLevel } from "@logtail/types";

import { Edge } from "./edge";

addEventListener("fetch", event => {
console.log(event)
})

/**
* Create a log with a random string / current date
*/
Expand All @@ -21,111 +19,49 @@ function getRandomLog(message: string): Partial<ILogtailLog> {

describe("edge tests", () => {
it("should echo log if logtail sends 20x status code", async () => {
nock("https://in.logtail.com")
.post('/')
.reply(201);

const message: string = String(Math.random());
const expectedLog = getRandomLog(message);
const edge = new Edge("valid source token");

edge.setSync(async logs => logs);

const echoedLog = await edge.log(message);
expect(echoedLog.message).toEqual(expectedLog.message);
});

it("should throw error if logtail sends non 200 status code", async () => {
nock("https://in.logtail.com")
.post('/')
.reply(401);

const edge = new Edge("invalid source token", { ignoreExceptions: false, throwExceptions: true });

edge.setSync(async () => { throw new Error("Mocked error in logging") });

const message: string = String(Math.random);
await expect(edge.log(message)).rejects.toThrow();
});

it("should warn and echo log even with circular reference as context", async () => {
nock("https://in.logtail.com")
.post('/')
.reply(201);

let circularContext: any = { foo: { value: 42 } };
circularContext.foo.bar = circularContext;

const message: string = String(Math.random());
const expectedLog = getRandomLog(message);
const edge = new Edge("valid source token");
const echoedLog = await edge.log(message, LogLevel.Info, circularContext);
expect(echoedLog.message).toEqual(expectedLog.message);
});

it("should enable piping logs to a writable stream", async () => {
// Create a writable stream
const writeStream = new Writable({
write(
chunk: any,
encoding: string,
callback: (error?: Error | null) => void
): void {
// Will be a buffered JSON string -- parse
const log: ILogtailLog = JSON.parse(chunk.toString());

// Expect the log to match the message
expect(log.message).toEqual(message);

callback();
}
});

// Fixtures
const logtail = new Edge("test");
logtail.pipe(writeStream);
edge.setSync(async logs => logs);

const message = "This should be streamed";

// Mock the sync method by simply returning the same logs
logtail.setSync(async logs => logs);

// Fire a log event
await logtail.log(message);
const echoedLog = await edge.log(message, LogLevel.Info, circularContext);
expect(echoedLog.message).toEqual(expectedLog.message);
});

it("should pipe logs to a writable file stream", async () => {
// Create a temporary file name
const temp = path.join(os.tmpdir(), `logtail_${Math.random()}`);

// Create a write stream based on that temp file
const writeStream = fs.createWriteStream(temp);

// Create a Pass-through stream, to ensure multiplexing works
const passThrough = new PassThrough();

// Pass write stream to Logtail
const logtail = new Edge("test");
logtail.pipe(passThrough).pipe(writeStream);

// Mock the sync method by simply returning the same logs
logtail.setSync(async logs => logs);

// Create messages
const messages = ["message 1", "message 2"];

// Log messages
await Promise.all(messages.map(msg => logtail.log(msg)));

writeStream.on("finish", () => {
// Get the stored data, and translate back to JSON
const data = fs
.readFileSync(temp)
.toString()
.trim()
.split("\n")
.map(line => JSON.parse(line));
it("should contain context info", async () => {
const message: string = String(Math.random());
const edge = new Edge("valid source token");

// Messages should match
for (let i = 0; i < messages.length; i++) {
expect(data[i].message).toEqual(messages[i]);
}
});
edge.setSync(async logs => logs);

writeStream.end();
const echoedLog = await edge.log(message);
expect(typeof echoedLog.context).toBe("object");
expect(typeof echoedLog.context.runtime).toBe("object");
expect(typeof echoedLog.context.runtime.file).toBe("string");
expect(typeof echoedLog.context.runtime.line).toBe("number");
});
});
29 changes: 3 additions & 26 deletions packages/edge/src/edge.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {Duplex, Writable} from "stream";

import {encode} from "@msgpack/msgpack";

import {Context, ILogLevel, ILogtailLog, ILogtailOptions, LogLevel, StackContextHint} from "@logtail/types";
Expand All @@ -8,12 +6,6 @@ import {Base} from "@logtail/core";
import {getStackContext} from "./context";

export class Edge extends Base {
/**
* Readable/Duplex stream where JSON stringified logs of type `ILogtailLog`
* will be pushed after syncing
*/
private _writeStream?: Writable | Duplex;

public constructor(
sourceToken: string,
options?: Partial<ILogtailOptions>
Expand Down Expand Up @@ -74,31 +66,16 @@ export class Edge extends Base {
context = { ...getStackContext(this, stackContextHint), ...context };
const processedLog = await super.log(message, level, context);

// Push the processed log to the stream, for piping
if (this._writeStream) {
this._writeStream.write(JSON.stringify(processedLog) + "\n");
}

// Return the transformed log
return processedLog as ILogtailLog & TContext;
}

/**
* Pipe JSON stringified `ILogtailLog` to a stream after syncing
*
* @param stream - Writable|Duplex stream
*/
public pipe(stream: Writable | Duplex) {
this._writeStream = stream;
return stream;
}

private encodeAsMsgpack(logs: ILogtailLog[]): Buffer {
private encodeAsMsgpack(logs: ILogtailLog[]): Uint8Array {
const maxDepth = this._options.contextObjectMaxDepth;
const logsWithISODateFormat = logs.map((log) => ({ ...this.sanitizeForEncoding(log, maxDepth), dt: log.dt.toISOString() }));
const encoded = encode(logsWithISODateFormat);
const buffer = Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength)
return buffer;

return new Uint8Array(encoded.buffer, encoded.byteOffset, encoded.byteLength)
}

private sanitizeForEncoding(value: any, maxDepth: number, visitedObjects: WeakSet<any> = new WeakSet()): any {
Expand Down
Loading

0 comments on commit c346166

Please sign in to comment.