Skip to content

Commit 348a404

Browse files
authored
migration: migrate to Pino (logger) (#1269)
Summary Migrates the HyperDX API and related services from Winston to Pino for standardized, faster, and more structured logging with improved OpenTelemetry integration. Changes Core Migration - Replaced Winston with Pino across all logging infrastructure - Upgraded @hyperdx/node-opentelemetry from v0.8.2 to v0.9.0 to support Pino transport - Removed deprecated dependencies: - winston and express-winston - @opentelemetry/host-metrics and @opentelemetry/sdk-metrics (consolidated into newer OTel SDK) - Added new dependencies: - pino and pino-http for core logging - pino-pretty for development console output Logger Configuration (packages/api/src/utils/logger.ts) Production: - Outputs stringified JSON to stdout via pino/file transport - Maintains HyperDX transport integration when API key is configured - Includes full OpenTelemetry trace context (trace_id, span_id, trace_flags) <img width="830" height="184" alt="Screenshot 2025-10-14 at 4 31 36 PM" src="https://github.com/user-attachments/assets/82e60919-5c4d-4688-a6f5-d54632aef749" /> Development: - Uses pino-pretty for human-readable, colorized console output - Hides verbose fields from console: pid, hostname, trace_id, span_id, trace_flags - HTTP request/response objects excluded from logs via custom serializers <img width="825" height="350" alt="image" src="https://github.com/user-attachments/assets/64b293d8-bc95-4715-931a-dbf73483d247" /> HTTP Logging: - Replaced express-winston with pino-http - Custom log levels based on HTTP status codes (warn for 4xx, error for 5xx+) - Simplified log messages: HTTP {method} {url} Error Logging Updates Updated error logging patterns throughout the codebase to follow Pino's structured logging conventions: // Before (Winston) logger.error('Error message:', error); // After (Pino) logger.error({ err: error }, 'Error message'); Ref: HDX-2588 This PR should also address issue: #1035
1 parent 43e32aa commit 348a404

File tree

22 files changed

+485
-330
lines changed

22 files changed

+485
-330
lines changed

.changeset/wet-stingrays-behave.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hyperdx/api": minor
3+
"@hyperdx/app": minor
4+
---
5+
6+
migration: migrate to Pino for standardized and faster logging

packages/api/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"@ai-sdk/anthropic": "^2.0.23",
1111
"@esm2cjs/p-queue": "^7.3.0",
1212
"@hyperdx/common-utils": "^0.7.0",
13-
"@hyperdx/node-opentelemetry": "^0.8.2",
13+
"@hyperdx/node-opentelemetry": "^0.9.0",
1414
"@hyperdx/passport-local-mongoose": "^9.0.1",
1515
"@opentelemetry/api": "^1.8.0",
1616
"@opentelemetry/host-metrics": "^0.35.5",
@@ -27,7 +27,6 @@
2727
"express": "^4.19.2",
2828
"express-rate-limit": "^6.7.1",
2929
"express-session": "^1.17.3",
30-
"express-winston": "^4.2.0",
3130
"handlebars": "^4.7.8",
3231
"http-graceful-shutdown": "^3.1.13",
3332
"http-proxy-middleware": "^3.0.5",
@@ -40,12 +39,13 @@
4039
"on-headers": "^1.1.0",
4140
"passport": "^0.6.0",
4241
"passport-local": "^1.0.0",
42+
"pino": "^10.0.0",
43+
"pino-http": "^11.0.0",
4344
"promised-handlebars": "^2.0.1",
4445
"protobufjs": "^7.5.2",
4546
"semver": "^7.5.2",
4647
"serialize-error": "^8.1.0",
4748
"uuid": "^8.3.2",
48-
"winston": "^3.10.0",
4949
"zod": "3.25",
5050
"zod-express-middleware": "^1.4.0"
5151
},
@@ -67,6 +67,7 @@
6767
"jest": "^28.1.3",
6868
"migrate-mongo": "^11.0.0",
6969
"nodemon": "^2.0.20",
70+
"pino-pretty": "^13.1.1",
7071
"rimraf": "^4.4.1",
7172
"supertest": "^6.3.1",
7273
"swagger-jsdoc": "^6.2.8",

packages/api/src/fixtures.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,7 @@ const healthCheck = async () => {
6363
const client = await getClickhouseClient();
6464
const result = await client.ping();
6565
if (!result.success) {
66-
logger.error({
67-
message: 'ClickHouse health check failed',
68-
error: result.error,
69-
});
66+
logger.error({ error: result.error }, 'ClickHouse health check failed');
7067
throw result.error;
7168
}
7269
};

packages/api/src/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if (config.IS_DEV) {
2121
const server = new Server();
2222

2323
process.on('uncaughtException', (err: Error) => {
24-
logger.error(serializeError(err));
24+
logger.error({ err: serializeError(err) }, 'Uncaught exception');
2525

2626
// FIXME: disable server restart until
2727
// we make sure all expected exceptions are handled properly
@@ -32,7 +32,9 @@ process.on('uncaughtException', (err: Error) => {
3232

3333
process.on('unhandledRejection', (err: any) => {
3434
// TODO: do we want to throw here ?
35-
logger.error(serializeError(err));
35+
logger.error({ err: serializeError(err) }, 'Unhandled rejection');
3636
});
3737

38-
server.start().catch(e => logger.error(serializeError(e)));
38+
server
39+
.start()
40+
.catch(e => logger.error({ err: serializeError(e) }, 'Server start failed'));

packages/api/src/middleware/auth.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export function redirectToDashboard(req: Request, res: Response) {
3131
return res.redirect(`${config.FRONTEND_URL}/search`);
3232
} else {
3333
logger.error(
34-
`Password login for user failed, user or team not found ${req?.user?._id}`,
34+
{ userId: req?.user?._id },
35+
'Password login for user failed, user or team not found',
3536
);
3637
res.redirect(`${config.FRONTEND_URL}/login?err=unknown`);
3738
}
@@ -43,7 +44,7 @@ export function handleAuthError(
4344
res: Response,
4445
next: NextFunction,
4546
) {
46-
logger.debug({ message: 'Auth error', authErr: serializeError(err) });
47+
logger.debug({ authErr: serializeError(err) }, 'Auth error');
4748
if (res.headersSent) {
4849
return next(err);
4950
}

packages/api/src/models/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ mongoose.connection.on('disconnected', () => {
2222
logger.info('Lost connection to MongoDB server');
2323
});
2424

25-
mongoose.connection.on('error', () => {
26-
logger.error('Could not connect to MongoDB');
25+
mongoose.connection.on('error', err => {
26+
logger.error({ err }, 'Could not connect to MongoDB');
2727
});
2828

2929
mongoose.connection.on('reconnected', () => {
30-
logger.error('Reconnected to MongoDB');
30+
logger.warn('Reconnected to MongoDB');
3131
});
3232

3333
mongoose.connection.on('reconnectFailed', () => {

packages/api/src/opamp/controllers/opampController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ export class OpampController {
303303

304304
// Decode the AgentToServer message
305305
const agentToServer = decodeAgentToServer(req.body);
306-
logger.debug('agentToServer', agentToServer);
306+
logger.debug({ agentToServer }, 'agentToServer');
307307
logger.debug(
308308
// @ts-ignore
309309
`Received message from agent: ${agentToServer.instanceUid?.toString(
@@ -353,7 +353,7 @@ export class OpampController {
353353
res.setHeader('Content-Type', 'application/x-protobuf');
354354
res.send(encodedResponse);
355355
} catch (error) {
356-
logger.error('Error handling OpAMP message:', error);
356+
logger.error({ err: error }, 'Error handling OpAMP message');
357357
res.status(500).send('Internal Server Error');
358358
}
359359
}

packages/api/src/opamp/services/agentService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class AgentService {
6363

6464
return agent;
6565
} catch (error) {
66-
logger.error('Error processing agent status:', error);
66+
logger.error({ err: error }, 'Error processing agent status');
6767
throw error;
6868
}
6969
}

packages/api/src/opamp/utils/protobuf.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ try {
2121
root = protobuf.loadSync(PROTO_PATH);
2222
logger.debug('OpAMP proto definition loaded successfully');
2323
} catch (error) {
24-
logger.error('Failed to load OpAMP proto definition:', error);
24+
logger.error({ err: error }, 'Failed to load OpAMP proto definition');
2525
throw error;
2626
}
2727

@@ -46,7 +46,7 @@ export function decodeAgentToServer(data: Buffer): protobuf.Message {
4646
try {
4747
return AgentToServer.decode(data);
4848
} catch (error) {
49-
logger.error('Failed to decode AgentToServer message:', error);
49+
logger.error({ err: error }, 'Failed to decode AgentToServer message');
5050
throw error;
5151
}
5252
}
@@ -68,7 +68,7 @@ export function encodeServerToAgent(message: any): Buffer {
6868
// Encode the message
6969
return Buffer.from(ServerToAgent.encode(serverToAgent).finish());
7070
} catch (error) {
71-
logger.error('Failed to encode ServerToAgent message:', error);
71+
logger.error({ err: error }, 'Failed to encode ServerToAgent message');
7272
throw error;
7373
}
7474
}
@@ -105,7 +105,7 @@ export function createRemoteConfig(
105105
configHash: configHash,
106106
};
107107
} catch (error) {
108-
logger.error('Failed to create remote config message:', error);
108+
logger.error({ err: error }, 'Failed to create remote config message');
109109
throw error;
110110
}
111111
}
@@ -130,7 +130,7 @@ function calculateConfigHash(configFiles: Map<string, Buffer>): Buffer {
130130

131131
return hash.digest();
132132
} catch (error) {
133-
logger.error('Failed to calculate config hash:', error);
133+
logger.error({ err: error }, 'Failed to calculate config hash');
134134
throw error;
135135
}
136136
}

packages/api/src/routers/api/ai.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ router.post(
188188
const source = await getSource(teamId.toString(), sourceId);
189189

190190
if (source == null) {
191-
logger.error({ message: 'invalid source id', sourceId, teamId });
191+
logger.error({ sourceId, teamId }, 'invalid source id');
192192
return res.status(400).json({
193193
error: 'Invalid source',
194194
});

0 commit comments

Comments
 (0)