Skip to content

Commit

Permalink
feat: add metric dimesion payload builder (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
wsbrunson committed Feb 12, 2024
1 parent 87d83c7 commit 648b0e4
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[ignore]
.*/node_modules/hermes-estree
.*/node_modules/hermes-parser
.*/node_modules/babel-plugin-flow-runtime
.*/node_modules/belter
.*/node_modules/flow-runtime
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ xunit.xml
flow-typed
.DS_Store
package-lock.json
.vscode/
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"webpack": "babel-node --config-file ./node_modules/@krakenjs/grumbler-scripts/config/.babelrc-node --plugins=transform-es2015-modules-commonjs ./node_modules/.bin/webpack --progress",
"format": "prettier --write --ignore-unknown .",
"format:check": "prettier --check .",
"test:unit": "vitest run --coverage",
"test": "npm run format:check && npm run lint && npm run flow-typed && npm run flow && npm run test:unit",
"vitest": "vitest",
"test": "npm run format:check && npm run lint && npm run flow-typed && npm run flow && npm run vitest -- run --coverage",
"build": "npm run test && npm run babel && npm run webpack",
"clean": "rimraf dist coverage",
"reinstall": "rimraf flow-typed && rimraf node_modules && npm install && flow-typed install",
Expand Down
18 changes: 18 additions & 0 deletions src/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export type LoggerType = {|
addPayloadBuilder: AddBuilder,
addMetaBuilder: AddBuilder,
addTrackingBuilder: AddBuilder,
addMetricDimensionBuilder: AddBuilder,
addHeaderBuilder: AddBuilder,

setTransport: (Transport) => LoggerType,
Expand Down Expand Up @@ -84,6 +85,7 @@ export function Logger({
const payloadBuilders: Array<Builder> = [];
const metaBuilders: Array<Builder> = [];
const trackingBuilders: Array<Builder> = [];
const metricDimensionBuilders: Array<Builder> = [];
const headerBuilders: Array<Builder> = [];

function print(
Expand Down Expand Up @@ -228,6 +230,10 @@ export function Logger({
return addBuilder(trackingBuilders, builder);
}

function addMetricDimensionBuilder(builder): LoggerType {
return addBuilder(metricDimensionBuilders, builder);
}

function addHeaderBuilder(builder): LoggerType {
return addBuilder(headerBuilders, builder);
}
Expand Down Expand Up @@ -270,6 +276,17 @@ export function Logger({
return logger; // eslint-disable-line no-use-before-define
}

if (metricDimensionBuilders.length > 0 && !metricPayload.dimensions) {
metricPayload.dimensions = {};
}

for (const builder of metricDimensionBuilders) {
extendIfDefined(
metricPayload.dimensions || {},
builder(metricPayload.dimensions || {})
);
}

print(
LOG_LEVEL.DEBUG,
`metric.${metricPayload.metricNamespace}`,
Expand Down Expand Up @@ -343,6 +360,7 @@ export function Logger({
immediateFlush,
addPayloadBuilder,
addMetaBuilder,
addMetricDimensionBuilder,
addTrackingBuilder,
addHeaderBuilder,
setTransport,
Expand Down
126 changes: 113 additions & 13 deletions src/logger.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable flowtype/require-valid-file-annotation, eslint-comments/disable-enable-pair */
/* @flow */
/* eslint-disable eslint-comments/disable-enable-pair */

import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, test, vi } from "vitest";

import { Logger } from ".";

Expand All @@ -15,6 +16,19 @@ vi.stubGlobal("XMLHttpRequest", XMLHttpRequestMock);
let logger;
let logBuf;

const initLogger = ({ url = "/test/api/log" } = {}) =>
Logger({
url,
});
const getLoggerBuffer = (testLogger) => testLogger.__buffer__;

const validMetricPayload = {
metricNamespace: "namespace",
metricEventName: "event",
metricType: "counter",
metricValue: 1,
};

describe("beaver exposes the Logger() function that returns a logger instance with the following methods... ", () => {
beforeEach(() => {
// create a new logger with a new buffer
Expand All @@ -29,7 +43,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
expect(logger.__buffer__.tracking).toHaveLength(0);
});

it(".info(evtName, payload?) sends an info event obj to the events[] buffer ready for logging", () => {
test(".info(evtName, payload?) sends an info event obj to the events[] buffer ready for logging", () => {
const expected = {
event: "testing",
level: "info",
Expand Down Expand Up @@ -57,7 +71,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
expect(logBuf.events[1]).toMatchObject(expectedWithPayload);
});

it(".warn(evtName, payload?) sends a warn event obj to the events[] buffer ready for logging", () => {
test(".warn(evtName, payload?) sends a warn event obj to the events[] buffer ready for logging", () => {
const expected = {
event: "testing",
level: "warn",
Expand All @@ -83,7 +97,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
expect(logBuf.events[1]).toMatchObject(expectedWithPayload);
});

it(".error(evtName, payload?) sends an error event obj to the events[] buffer ready for logging", () => {
test(".error(evtName, payload?) sends an error event obj to the events[] buffer ready for logging", () => {
const expected = {
event: "house is on fire!!!",
level: "error",
Expand All @@ -109,7 +123,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
expect(logBuf.events[1]).toMatchObject(expectedWithPayload);
});

it(".debug(evtName, payload?) sends a debug event obj to the events[] buffer ready for logging", () => {
test(".debug(evtName, payload?) sends a debug event obj to the events[] buffer ready for logging", () => {
const expected = {
event: "gollum_personality_count",
level: "debug",
Expand All @@ -135,7 +149,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
expect(logBuf.events[1]).toMatchObject(expectedWithPayload);
});

it(".track(payload) sends general tracking info to the tracking[] buffer and will be sent with next flush", () => {
test(".track(payload) sends general tracking info to the tracking[] buffer and will be sent with next flush", () => {
const expected = {
token: "12345",
country: "FR",
Expand All @@ -151,9 +165,10 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
expect(logBuf.tracking[0]).toMatchObject(expected);
});

it(".metric(payload) sends general tracking info to the metrics[] buffer and will be sent with next flush", () => {
test(".metric(payload) sends general tracking info to the metrics[] buffer and will be sent with next flush", () => {
const expected = {
name: "pp.xo.ui.lite.weasley.fallback-error",
metricNamespace: "pp.xo.ui.lite.weasley.fallback-error",
metricEventName: "event",
metricValue: 5,
dimensions: {
country: "FR",
Expand All @@ -162,6 +177,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
};

// 1) Won't error on empty / missing payload props
// $FlowIssue invalid metric payload
logger.metric({});
expect(logBuf.metrics).toHaveLength(1);
expect(logBuf.metrics[0]).toMatchObject({});
Expand All @@ -174,7 +190,7 @@ describe("beaver exposes the Logger() function that returns a logger instance wi
});

describe("a beaver-logger instance exposes logger.__buffer__={} prop as a readonly object that...", () => {
it("doesn't allow its immedate children props to be reassigned", () => {
test("doesn't allow its immedate children props to be reassigned", () => {
const loggerInstance = Logger({
url: "/test/api/log",
});
Expand All @@ -187,17 +203,22 @@ describe("a beaver-logger instance exposes logger.__buffer__={} prop as a readon
});

// verify we disallow re-assigning __buffer__ or its arrays
// $FlowIssue Invalid buffer
expect(() => (loggerInstance.__buffer__ = {})).toThrow(
"Cannot assign to read only property '__buffer__' of object '#<Object>'"
);

// $FlowIssue Invalid buffer
expect(() => (loggerInstance.__buffer__.events = [])).toThrow(
"Cannot set property events of #<Object> which has only a getter"
);

// but using the buffer array methods directly is still allowed
// $FlowIssue Invalid buffer
loggerInstance.__buffer__.metrics.push(1, 2, 3);
// $FlowIssue Invalid buffer
loggerInstance.__buffer__.events.push(2);
// $FlowIssue Invalid buffer
loggerInstance.__buffer__.tracking.push(3);

expect(loggerInstance.__buffer__).toStrictEqual({
Expand All @@ -223,19 +244,19 @@ describe("beaver logger provides flushing methods that send events to the server
expect(logBuf.metrics).toHaveLength(0);
});

it(".flush() clears the buffers (after sending data to server))", async () => {
test(".flush() clears the buffers (after sending data to server))", async () => {
logger.info("testing");
expect(logBuf.events).toHaveLength(1);

await logger.flush();
expect(logBuf.events).toHaveLength(0);
});

// it(".flush() debounces then calls immediateFlush()) ", async () => {
// test(".flush() debounces then calls immediateFlush()) ", async () => {
// TODO: need to test the promiseDebounce. Was unable to figure out how to do that. (zalgo...)
// });

it(".immediateflush() clears the buffers (after sending data to server))", async () => {
test(".immediateflush() clears the buffers (after sending data to server))", async () => {
logger.info("testing");
expect(logBuf.events).toHaveLength(1);

Expand All @@ -244,6 +265,85 @@ describe("beaver logger provides flushing methods that send events to the server
});
});

describe("addMetricDimensionBuilder", () => {
test("should add dimensions from builder", () => {
const testLogger = initLogger();

testLogger.addMetricDimensionBuilder(() => ({
dimension1: "1",
dimension2: "2",
dimension3: "3",
}));

testLogger.metric(validMetricPayload);

expect(getLoggerBuffer(testLogger).metrics[0]).toEqual(
expect.objectContaining({
dimensions: {
dimension1: "1",
dimension2: "2",
dimension3: "3",
},
})
);
});

test("should overwrite existing dimensions", () => {
const testLogger = initLogger();

testLogger.addMetricDimensionBuilder(() => ({
dimension1: "overwrite1",
dimension2: "overwrite2",
dimension3: "overwrite3",
}));

testLogger.metric({
...validMetricPayload,
dimensions: {
dimension1: "1",
dimension2: "2",
dimension3: "3",
},
});

expect(getLoggerBuffer(testLogger).metrics[0]).toEqual(
expect.objectContaining({
dimensions: {
dimension1: "overwrite1",
dimension2: "overwrite2",
dimension3: "overwrite3",
},
})
);
});

test("should merge dimensions", () => {
const testLogger = initLogger();

testLogger.addMetricDimensionBuilder(() => ({
dimension1: "1",
dimension3: "3",
}));

testLogger.metric({
...validMetricPayload,
dimensions: {
dimension2: "2",
},
});

expect(getLoggerBuffer(testLogger).metrics[0]).toEqual(
expect.objectContaining({
dimensions: {
dimension1: "1",
dimension2: "2",
dimension3: "3",
},
})
);
});
});

/*
TODO: Need to write unit tests for these exposed methods:
addPayloadBuilder,
Expand Down

0 comments on commit 648b0e4

Please sign in to comment.