Skip to content

Commit c2d04cb

Browse files
authored
Merge pull request #1302 from joshunrau/debug-docs
format gateway logs as json, update express to v5 on gateway, add debug page to docs
2 parents 45033d3 + 1b71956 commit c2d04cb

File tree

8 files changed

+100
-392
lines changed

8 files changed

+100
-392
lines changed

apps/gateway/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@
2525
"@prisma/client": "catalog:",
2626
"axios": "catalog:",
2727
"compression": "^1.7.5",
28-
"express": "^4.21.2",
28+
"express": "^5.2.1",
2929
"lodash-es": "workspace:lodash-es__4.x@*",
30-
"pino-http": "^10.4.0",
31-
"pino-pretty": "^11.2.2",
30+
"pino": "^10.3.1",
31+
"pino-http": "^11.0.0",
32+
"pino-pretty": "^13.1.3",
3233
"react": "workspace:react__19.x@*",
3334
"react-dom": "workspace:react-dom__19.x@*",
3435
"sirv": "^2.0.4",
@@ -41,7 +42,7 @@
4142
"@opendatacapture/vite-plugin-runtime": "workspace:*",
4243
"@tailwindcss/vite": "catalog:",
4344
"@types/compression": "^1.7.5",
44-
"@types/express": "^4.17.21",
45+
"@types/express": "^5.0.6",
4546
"@vitejs/plugin-react-swc": "^3.7.2",
4647
"esbuild": "catalog:",
4748
"nodemon": "catalog:",

apps/gateway/src/logger.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pino from 'pino';
2+
3+
export const logger = pino(
4+
{},
5+
{
6+
write: (message) => {
7+
process.stdout.write(JSON.stringify(JSON.parse(message), null, 2) + '\n');
8+
}
9+
}
10+
);

apps/gateway/src/middleware/error-handler.middleware.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { ErrorRequestHandler } from 'express';
22

3+
import { logger } from '@/logger';
34
import { HttpException } from '@/utils/http-exception';
45

56
export const errorHandlerMiddleware: ErrorRequestHandler = (err, _, res, next) => {
6-
console.error(err);
7+
logger.error(err);
78
if (res.headersSent) {
89
return next(err);
910
} else if (err instanceof HttpException) {

apps/gateway/src/routers/api.router.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { GatewayHealthcheckSuccessResult } from '@opendatacapture/schemas/g
99
import { Router } from 'express';
1010

1111
import { prisma } from '@/lib/prisma';
12+
import { logger } from '@/logger';
1213
import { ah } from '@/utils/async-handler';
1314
import { HttpException } from '@/utils/http-exception';
1415

@@ -42,7 +43,7 @@ router.post(
4243
ah(async (req, res) => {
4344
const result = await $CreateRemoteAssignmentData.safeParseAsync(req.body);
4445
if (!result.success) {
45-
console.error(result.error.issues);
46+
logger.error(result.error.issues);
4647
throw new HttpException(400, 'Bad Request');
4748
}
4849
const { instrumentContainer, publicKey, ...assignment } = result.data;
@@ -61,7 +62,7 @@ router.post(
6162
router.patch(
6263
'/assignments/:id',
6364
ah(async (req, res) => {
64-
const id = req.params.id;
65+
const id = req.params.id as string;
6566
const assignment = await prisma.remoteAssignmentModel.findFirst({
6667
where: { id }
6768
});
@@ -70,7 +71,7 @@ router.patch(
7071
}
7172
const result = await $UpdateRemoteAssignmentData.safeParseAsync(req.body);
7273
if (!result.success) {
73-
console.error(result.error.issues);
74+
logger.error(result.error.issues);
7475
throw new HttpException(400, 'Bad Request');
7576
}
7677
const { data, kind, status } = result.data;
@@ -114,7 +115,7 @@ router.patch(
114115
router.delete(
115116
'/assignments/:id',
116117
ah(async (req, res) => {
117-
const id = req.params.id;
118+
const id = req.params.id as string;
118119
const assignment = await prisma.remoteAssignmentModel.findFirst({
119120
where: { id }
120121
});

apps/gateway/src/routers/root.router.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { $InstrumentBundleContainer } from '@opendatacapture/schemas/instrument'
22
import { Router } from 'express';
33

44
import { prisma } from '@/lib/prisma';
5+
import { logger } from '@/logger';
56
import type { RootProps } from '@/Root';
67
import { ah } from '@/utils/async-handler';
78
import { generateToken } from '@/utils/auth';
@@ -11,7 +12,7 @@ const router = Router();
1112
router.get(
1213
'/assignments/:id',
1314
ah(async (req, res, next) => {
14-
const id = req.params.id!;
15+
const id = req.params.id! as string;
1516
const assignment = await prisma.remoteAssignmentModel.findFirst({
1617
where: { id }
1718
});
@@ -26,7 +27,7 @@ router.get(
2627

2728
const targetParseResult = await $InstrumentBundleContainer.safeParseAsync(JSON.parse(assignment.targetStringified));
2829
if (!targetParseResult.success) {
29-
console.error(targetParseResult.error.issues);
30+
logger.error(targetParseResult.error.issues);
3031
return res.status(500).set({ 'Content-Type': 'application/json' }).json({
3132
error: 'Internal Server Error',
3233
message: 'Failed to parse target instrument from database',

apps/gateway/src/server/server.base.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import express from 'express';
22
import type { Request, Response } from 'express';
33
import PinoHttp from 'pino-http';
4-
import PinoPretty from 'pino-pretty';
54
import type { Promisable } from 'type-fest';
65

76
import { config } from '@/config';
87
import type { RenderFunction } from '@/entry-server';
8+
import { logger } from '@/logger';
99
import { apiKeyMiddleware } from '@/middleware/api-key.middleware';
1010
import { errorHandlerMiddleware } from '@/middleware/error-handler.middleware';
1111
import type { RootProps } from '@/Root';
@@ -31,10 +31,8 @@ export abstract class BaseServer {
3131
} catch (err) {
3232
if (err instanceof Error) {
3333
this.fixStacktrace?.(err);
34-
console.error(err.stack);
35-
} else {
36-
console.error(err);
3734
}
35+
logger.error(err);
3836
res.status(500).json({ message: 'Internal Server Error', statusCode: 500 });
3937
}
4038
});
@@ -47,25 +45,21 @@ export abstract class BaseServer {
4745
})
4846
);
4947
this.app.use(
50-
PinoHttp(
51-
{
52-
customLogLevel: (_, res) => {
53-
return res.statusCode >= 500 ? 'error' : 'info';
54-
},
55-
serializers: {
56-
req: (req: Request) => {
57-
return `${req.method} ${req.url}`;
58-
},
59-
res: (res: Response) => {
60-
return res.statusCode;
61-
}
48+
PinoHttp({
49+
customLogLevel: (_, res) => {
50+
return res.statusCode >= 500 ? 'error' : 'info';
51+
},
52+
logger,
53+
serializers: {
54+
req: (req: Request) => {
55+
return `${req.method} ${req.url}`;
6256
},
63-
wrapSerializers: false
57+
res: (res: Response) => {
58+
return res.statusCode;
59+
}
6460
},
65-
PinoPretty({
66-
colorize: true
67-
})
68-
)
61+
wrapSerializers: false
62+
})
6963
);
7064
this.app.use('/api', apiKeyMiddleware, apiRouter);
7165
this.app.use('/', this.rootLoader, rootRouter);
@@ -76,8 +70,7 @@ export abstract class BaseServer {
7670

7771
listen(port = config.port) {
7872
return this.app.listen(port, () => {
79-
// eslint-disable-next-line no-console
80-
console.log(`Server started at http://localhost:${port}`);
73+
logger.info(`Server started at http://localhost:${port}`);
8174
});
8275
}
8376

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: Debugging
3+
slug: en/docs/tutorials/debugging
4+
sidebar:
5+
order: 2
6+
---
7+
8+
When debugging, it’s important to distinguish between **client-side errors** (errors in the user’s browser) and **server-side errors** (errors from your backend services). Client-side issues appear in the browser console, while server-side issues are recorded by your running containers.
9+
10+
### Client Logs
11+
12+
To inspect client-side errors and warnings, open your browser’s developer tools and check the **Console** tab.
13+
14+
### Server Logs
15+
16+
Server logs are available through Docker. For example, you can view logs for a specific service with:
17+
18+
```bash
19+
docker compose logs api
20+
```
21+
22+
The API and Gateway logs are in JSON format, so you can filter and inspect them using jq. For example, to show only error-level logs from the last 24 hours:
23+
24+
```bash
25+
docker compose logs api --no-log-prefix --since 24h | jq 'select(.level == "ERROR")'
26+
```
27+
28+
### Reporting Issues
29+
30+
If you encounter **500 Internal Server Errors** or unclear failures, please open an issue on our GitHub repository and include:
31+
32+
- Relevant server logs (redacted if necessary)
33+
- Steps to reproduce the problem

0 commit comments

Comments
 (0)