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

Frontend logging: more generic context #805

Merged
merged 4 commits into from
Sep 25, 2024
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
68 changes: 23 additions & 45 deletions src/services/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,39 @@ const attemptFaroWarn = (msg: string, context?: LogContext) => {
}
};

const isRecord = (obj: unknown): obj is Record<string, unknown> => typeof obj === 'object';
/**
* Checks unknown error for properties from FetchError and adds them to the context
* Property names from the error are prepended with an underscore when added to the error context sent to Faro
* Note this renames the "message" to "_errorMessage" in hopes of reducing conflicts of future use of that property name
* Checks unknown error for properties from Records like FetchError and adds them to the context
* @param err
* @param context
*/
function populateFetchErrorContext(err: unknown | FetchError, context: LogContext) {
if (typeof err === 'object' && err !== null) {
if (hasTraceId(err) && typeof err.traceId === 'string') {
context._traceId = err.traceId;
}
if (hasMessage(err) && typeof err.message === 'string') {
// If we have a conflicting `_errorMessage`, move the original value to `_contextMessage`
if (context._errorMessage !== undefined) {
context._contextMessage = context._errorMessage;
}
context._errorMessage = err.message;
}
// @todo, if the request was cancelled, do we want to log the error?
if (hasCancelled(err) && typeof err.cancelled === 'boolean' && context.cancelled) {
context._cancelled = err.cancelled.toString();
}
if (hasStatusText(err) && typeof err.statusText === 'string') {
context._statusText = err.statusText;
}
if (hasHandled(err) && typeof err.isHandled === 'boolean') {
context._isHandled = err.isHandled.toString();
if (isRecord(err)) {
Object.keys(err).forEach((key: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better 👌

const value = err[key];
if (typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number') {
context[key] = value.toString();
}
});
}

if (hasData(err)) {
try {
context._data = JSON.stringify(err.data);
} catch (e) {
// do nothing
if (typeof err.data === 'object' && err.data !== null) {
try {
context.data = JSON.stringify(err.data);
} catch (e) {
// do nothing
}
} else if (typeof err.data === 'string' || typeof err.data === 'boolean' || typeof err.data === 'number') {
context.data = err.data.toString();
}
}
}
}

const attemptFaroErr = (err: Error | FetchError | unknown, context: LogContext) => {
const attemptFaroErr = (err: Error | FetchError | unknown, context2: LogContext) => {
let context = context2;
try {
populateFetchErrorContext(err, context);

Expand All @@ -90,10 +83,10 @@ const attemptFaroErr = (err: Error | FetchError | unknown, context: LogContext)
} else if (typeof err === 'string') {
logError(new Error(err), context);
} else if (err && typeof err === 'object') {
if (context._errorMessage) {
logError(new Error(context._errorMessage), context);
if (context.msg) {
logError(new Error(context.msg), context);
} else {
logError(new Error('unknown error'), context);
logError(new Error('error object'), context);
}
} else {
logError(new Error('unknown error'), context);
Expand All @@ -103,21 +96,6 @@ const attemptFaroErr = (err: Error | FetchError | unknown, context: LogContext)
}
};

const hasMessage = (value: object): value is { message: unknown } => {
return 'message' in value;
};
const hasTraceId = (value: object): value is { traceId: unknown } => {
return 'traceId' in value;
};
const hasStatusText = (value: object): value is { statusText: unknown } => {
return 'statusText' in value;
};
const hasCancelled = (value: object): value is { cancelled: unknown } => {
return 'cancelled' in value;
};
const hasData = (value: object): value is { data: unknown } => {
return 'data' in value;
};
const hasHandled = (value: object): value is { isHandled: unknown } => {
return 'isHandled' in value;
};
11 changes: 7 additions & 4 deletions tests/exploreServices.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { ExplorePage } from './fixtures/explore';
import { testIds } from '../src/services/testIds';
import { mockVolumeApiResponse } from './mocks/mockVolumeApiResponse';
import { mockLabelsResponse } from './mocks/mockLabelsResponse';
import { ConsoleMessage } from '@playwright/test';

test.describe('explore services page', () => {
let explorePage: ExplorePage;

test.beforeEach(async ({ page }) => {
explorePage = new ExplorePage(page);
test.beforeEach(async ({ page }, testInfo) => {
explorePage = new ExplorePage(page, testInfo);
await explorePage.setDefaultViewportSize();
await page.evaluate(() => window.localStorage.clear());
await explorePage.clearLocalStorage();
await explorePage.gotoServices();
explorePage.captureConsoleLogs();
});

test.afterEach(async ({ page }) => {
await page.unrouteAll({ behavior: 'ignoreErrors' });
await explorePage.unroute();
explorePage.echoConsoleLogsOnRetry();
});

test('should filter service labels on search', async ({ page }) => {
Expand Down
11 changes: 7 additions & 4 deletions tests/exploreServicesBreakDown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ const labelName = 'cluster';
test.describe('explore services breakdown page', () => {
let explorePage: ExplorePage;

test.beforeEach(async ({ page }) => {
explorePage = new ExplorePage(page);
test.beforeEach(async ({ page }, testInfo) => {
explorePage = new ExplorePage(page, testInfo);

await explorePage.setExtraTallViewportSize();
await page.evaluate(() => window.localStorage.clear());
await explorePage.clearLocalStorage();
await explorePage.gotoServicesBreakdown();
explorePage.blockAllQueriesExcept({
refIds: ['logsPanelQuery', fieldName],
legendFormats: [`{{${levelName}}}`],
});
explorePage.captureConsoleLogs();
});

test.afterEach(async ({ page }) => {
await page.unrouteAll({ behavior: 'ignoreErrors' });
await explorePage.unroute();
explorePage.echoConsoleLogsOnRetry();
});

test('should filter logs panel on search for broadcast field', async ({ page }) => {
Expand Down
10 changes: 6 additions & 4 deletions tests/exploreServicesJsonBreakDown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@ const fieldName = 'method';
test.describe('explore nginx-json breakdown pages ', () => {
let explorePage: ExplorePage;

test.beforeEach(async ({ page }) => {
explorePage = new ExplorePage(page);
test.beforeEach(async ({ page }, testInfo) => {
explorePage = new ExplorePage(page, testInfo);
await explorePage.setExtraTallViewportSize();
await page.evaluate(() => window.localStorage.clear());
await explorePage.clearLocalStorage();
await explorePage.gotoServicesBreakdown('nginx-json');
explorePage.blockAllQueriesExcept({
refIds: ['logsPanelQuery', fieldName],
});
explorePage.captureConsoleLogs();
});

test.afterEach(async ({ page }) => {
await page.unrouteAll({ behavior: 'ignoreErrors' });
await explorePage.unroute();
explorePage.echoConsoleLogsOnRetry();
});

test(`should exclude ${fieldName}, request should contain json`, async ({ page }) => {
Expand Down
9 changes: 5 additions & 4 deletions tests/exploreServicesJsonMixedBreakDown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ const serviceName = 'nginx-json-mixed';
test.describe('explore nginx-json-mixed breakdown pages ', () => {
let explorePage: ExplorePage;

test.beforeEach(async ({ page }) => {
explorePage = new ExplorePage(page);
test.beforeEach(async ({ page }, testInfo) => {
explorePage = new ExplorePage(page, testInfo);
await explorePage.setExtraTallViewportSize();
await page.evaluate(() => window.localStorage.clear());
await explorePage.clearLocalStorage();
await explorePage.gotoServicesBreakdown(serviceName);
explorePage.blockAllQueriesExcept({
refIds: ['logsPanelQuery', mixedFieldName],
});
});

test.afterEach(async ({ page }) => {
await page.unrouteAll({ behavior: 'ignoreErrors' });
await explorePage.unroute();
explorePage.echoConsoleLogsOnRetry();
});

test(`should exclude ${mixedFieldName}, request should contain both parsers`, async ({ page }) => {
Expand Down
28 changes: 26 additions & 2 deletions tests/fixtures/explore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Locator, Page } from '@playwright/test';
import { ConsoleMessage, Locator, Page, TestInfo } from '@playwright/test';
import pluginJson from '../../src/plugin.json';
import { testIds } from '../../src/services/testIds';
import { expect } from '@grafana/plugin-e2e';
Expand All @@ -15,8 +15,9 @@ export class ExplorePage {
serviceBreakdownSearch: Locator;
serviceBreakdownOpenExplore: Locator;
refreshPicker: Locator;
logs: Array<{ msg: ConsoleMessage; type: string }> = [];

constructor(public readonly page: Page) {
constructor(public readonly page: Page, public readonly testInfo: TestInfo) {
this.firstServicePageSelect = this.page.getByText('Select').first();
this.logVolumeGraph = this.page.getByText('Log volume');
this.servicesSearch = this.page.getByTestId(testIds.exploreServiceSearch.search);
Expand All @@ -25,6 +26,22 @@ export class ExplorePage {
this.refreshPicker = this.page.getByTestId(testIds.header.refreshPicker);
}

captureConsoleLogs() {
this.page.on('console', (msg) => {
this.logs.push({ msg, type: msg.type() });
});
}

echoConsoleLogsOnRetry() {
if (this.testInfo.retry > 0) {
console.log('logs', this.logs);
}
}

async clearLocalStorage() {
await this.page.evaluate(() => window.localStorage.clear());
}

async setDefaultViewportSize() {
await this.page.setViewportSize({ width: 1280, height: 720 });
}
Expand All @@ -36,6 +53,13 @@ export class ExplorePage {
});
}

/**
* Clears any custom routes created with page.route
*/
async unroute() {
await this.page.unrouteAll({ behavior: 'ignoreErrors' });
}

async gotoServices() {
await this.page.goto(`/a/${pluginJson.id}/explore`);
}
Expand Down
Loading