Skip to content

Commit 4f905e4

Browse files
authored
feat(app): add structured server-side logging (#745)
1 parent 883a99b commit 4f905e4

9 files changed

Lines changed: 80 additions & 13 deletions

app/backend/src/acceptVisualChanges.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { logEvent } from './logger';
12
import { S3Client } from './s3Client';
23
import {
34
BASE_IMAGE_NAME,
@@ -26,14 +27,21 @@ export const acceptVisualChanges = async ({
2627
if (reasonToPreventUpdate) {
2728
throw new TRPCError({
2829
code: 'FORBIDDEN',
29-
message: reasonToPreventUpdate
30+
message: reasonToPreventUpdate,
31+
cause: {
32+
event: 'VISUAL_CHANGE_ACCEPTANCE_BLOCKED',
33+
owner,
34+
repo,
35+
commitHash
36+
}
3037
});
3138
}
3239
const hash = commitHash ?? diffId;
3340
if (!hash) {
3441
throw new TRPCError({
3542
code: 'BAD_REQUEST',
36-
message: 'Please provide either a commitHash or a diffId.'
43+
message: 'Please provide either a commitHash or a diffId.',
44+
cause: { event: 'MISSING_IDENTIFIER', owner, repo }
3745
});
3846
}
3947

@@ -53,6 +61,13 @@ export const acceptVisualChanges = async ({
5361
if (commitHash) {
5462
await updateCommitStatus({ owner, repo, commitHash });
5563
}
64+
logEvent('INFO', {
65+
event: 'VISUAL_CHANGES_ACCEPTED',
66+
owner,
67+
repo,
68+
hash,
69+
baseImagesUpdated: useBaseImages
70+
});
5671
};
5772

5873
export const filterNewImages = (s3Paths: string[]) => {

app/backend/src/fetchCurrentPage.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ export const fetchCurrentPage = async ({
1414
if (!currentPage?.keys) {
1515
throw new TRPCError({
1616
code: 'NOT_FOUND',
17-
message: `Page ${page} does not exist. Only ${paginatedKeys.length} pages were found.`
17+
message: `Page ${page} does not exist. Only ${paginatedKeys.length} pages were found.`,
18+
cause: {
19+
event: 'PAGE_NOT_FOUND',
20+
hash,
21+
bucket,
22+
page,
23+
totalPages: paginatedKeys.length
24+
}
1825
});
1926
}
2027
const { keys, title } = currentPage;
@@ -24,7 +31,8 @@ export const fetchCurrentPage = async ({
2431
if (!result.success) {
2532
throw new TRPCError({
2633
code: 'BAD_REQUEST',
27-
message: `Invalid file name: ${key}. ${result.error.message}`
34+
message: `Invalid file name: ${key}. ${result.error.message}`,
35+
cause: { event: 'INVALID_FILE_NAME', hash, bucket, key }
2836
});
2937
}
3038

app/backend/src/getGroupedKeys.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export const getGroupedKeys = async (hash: string, bucket: string) => {
1010
throw new TRPCError({
1111
code: 'NOT_FOUND',
1212
message:
13-
'The commit hash was not associated with any visual regression test failures.'
13+
'The commit hash was not associated with any visual regression test failures.',
14+
cause: { event: 'COMMIT_HASH_NOT_FOUND', hash, bucket }
1415
});
1516
}
1617

@@ -34,7 +35,8 @@ export const getGroupedKeys = async (hash: string, bucket: string) => {
3435
throw new TRPCError({
3536
code: 'NOT_FOUND',
3637
message:
37-
'There was no new or diff images associated with the commit hash.\nThis might be because the tests failed before a picture could be taken and it could be compared to the base.'
38+
'There was no new or diff images associated with the commit hash.\nThis might be because the tests failed before a screenshot could be taken and it could be compared to the base.',
39+
cause: { event: 'NO_NEW_OR_DIFF_IMAGES', hash, bucket }
3840
});
3941
}
4042

app/backend/src/getOctokit.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,26 @@ export const getOctokit = (owner: string, repo: string) => {
88
if (!existsSync(secretsFilePath)) {
99
throw new TRPCError({
1010
code: 'UNAUTHORIZED',
11-
message: 'Missing secrets.json file'
11+
message: 'Missing secrets.json file',
12+
cause: { event: 'MISSING_SECRETS_FILE', owner, repo, secretsFilePath }
1213
});
1314
}
1415
const parsedJson = JSON.parse(readFileSync(secretsFilePath).toString());
1516
const result = secretsJsonSchema.safeParse(parsedJson);
1617
if (!result.success) {
1718
throw new TRPCError({
1819
code: 'INTERNAL_SERVER_ERROR',
19-
message: `Failed to parse secrets.json: ${result.error}`
20+
message: `Failed to parse secrets.json: ${result.error}`,
21+
cause: { event: 'SECRETS_PARSE_ERROR', owner, repo, error: result.error }
2022
});
2123
}
2224
const repoSecrets = result.data[`${owner}/${repo}`];
2325
const { githubToken, githubApiUrl } = repoSecrets ?? {};
2426
if (!githubToken) {
2527
throw new TRPCError({
2628
code: 'UNAUTHORIZED',
27-
message: `Missing githubToken for repo ${owner}/${repo}`
29+
message: `Missing githubToken for repo ${owner}/${repo}`,
30+
cause: { event: 'MISSING_GITHUB_TOKEN', owner, repo }
2831
});
2932
}
3033
return new Octokit({

app/backend/src/logger.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const logEvent = (
2+
level: 'INFO' | 'WARN' | 'ERROR',
3+
payload: Record<string, unknown>
4+
) => {
5+
// eslint-disable-next-line no-console
6+
console.log(
7+
JSON.stringify({ timestamp: new Date().toISOString(), level, ...payload })
8+
);
9+
};

app/backend/src/updateCommitStatus.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ export const updateCommitStatus = async ({
2121
.catch(error => {
2222
throw new TRPCError({
2323
code: 'INTERNAL_SERVER_ERROR',
24-
message: `Failed to update GitHub commit status: ${error}`
24+
message: `Failed to update GitHub commit status: ${error}`,
25+
cause: {
26+
event: 'COMMIT_STATUS_UPDATE_FAILED',
27+
owner,
28+
repo,
29+
commitHash,
30+
error
31+
}
2532
});
2633
});
2734
};

app/backend/test/getGroupedKeys.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ describe('getGroupedKeys', () => {
102102
]
103103
}));
104104
expect(getGroupedKeys('hash', 'bucket')).rejects.toThrow(
105-
'There was no new or diff images associated with the commit hash.\nThis might be because the tests failed before a picture could be taken and it could be compared to the base.'
105+
'There was no new or diff images associated with the commit hash.\nThis might be because the tests failed before a screenshot could be taken and it could be compared to the base.'
106106
);
107107
});
108108
});

app/server.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { router } from './backend/src/router';
2+
import { logEvent } from './backend/src/logger';
23
import { createBunHttpHandler } from 'trpc-bun-adapter';
34
import { serve, file } from 'bun';
45
import { join } from 'path';
@@ -7,14 +8,29 @@ import index from './public/index.html';
78
const IS_PROD = process.env.NODE_ENV === 'production';
89
const DIST_DIR = join(import.meta.dir, 'dist');
910

11+
const trpcHandler = createBunHttpHandler({
12+
router,
13+
endpoint: '/trpc',
14+
onError: ({ error: { code, message, cause }, path }) => {
15+
const event =
16+
cause && 'event' in cause && typeof cause.event === 'string'
17+
? cause.event
18+
: 'UNKNOWN';
19+
const causeWithEvent = {
20+
...cause,
21+
event
22+
};
23+
logEvent('ERROR', { path, code, message, cause: causeWithEvent });
24+
}
25+
});
26+
1027
const server = serve({
1128
routes: {
1229
'/': IS_PROD ? file(join(DIST_DIR, 'index.html')) : index,
1330
'/health': new Response('healthy', { status: 200 })
1431
},
1532
port: process.env.PORT ?? 8080,
1633
async fetch(request, response) {
17-
const trpcHandler = createBunHttpHandler({ router, endpoint: '/trpc' });
1834
const trpcResponse = trpcHandler(request, response);
1935
if (trpcResponse) return trpcResponse;
2036

@@ -29,4 +45,4 @@ const server = serve({
2945
}
3046
});
3147

32-
console.log(`Server running at ${server.url}`);
48+
logEvent('INFO', { message: `Server running at ${server.url}` });

eslint.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ export default [
2323
'@typescript-eslint/no-namespace': 'off'
2424
}
2525
},
26+
{
27+
files: ['app/**/*.ts', 'app/**/*.tsx'],
28+
ignores: ['app/build.ts'],
29+
rules: {
30+
'no-console': ['error']
31+
}
32+
},
2633
{
2734
ignores: [
2835
'**/.tsup',

0 commit comments

Comments
 (0)