mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
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: https://github.com/hyperdxio/hyperdx/issues/1035
90 lines
2.5 KiB
TypeScript
90 lines
2.5 KiB
TypeScript
import { CronJob } from 'cron';
|
|
import minimist from 'minimist';
|
|
import { performance } from 'perf_hooks';
|
|
import { serializeError } from 'serialize-error';
|
|
|
|
import { RUN_SCHEDULED_TASKS_EXTERNALLY } from '@/config';
|
|
import CheckAlertTask from '@/tasks/checkAlerts';
|
|
import PingPongTask from '@/tasks/pingPongTask';
|
|
import { asTaskArgs, HdxTask, TaskArgs } from '@/tasks/types';
|
|
import logger from '@/utils/logger';
|
|
|
|
import { tasksTracer } from './tracer';
|
|
|
|
function createTask(argv: TaskArgs): HdxTask<TaskArgs> {
|
|
const taskName = argv.taskName;
|
|
switch (taskName) {
|
|
case 'check-alerts':
|
|
return new CheckAlertTask(argv);
|
|
case 'ping-pong':
|
|
return new PingPongTask(argv);
|
|
default:
|
|
throw new Error(`Unknown task name ${taskName}`);
|
|
}
|
|
}
|
|
|
|
const main = async (argv: TaskArgs) => {
|
|
await tasksTracer.startActiveSpan(argv.taskName || 'task', async span => {
|
|
const task: HdxTask<TaskArgs> = createTask(argv);
|
|
try {
|
|
const t0 = performance.now();
|
|
logger.info(`Task [${task.name()}] started at ${new Date()}`);
|
|
await task.execute();
|
|
logger.info(
|
|
`Task [${task.name()}] finished in ${(performance.now() - t0).toFixed(2)} ms`,
|
|
);
|
|
} catch (e: unknown) {
|
|
logger.error(
|
|
{
|
|
cause: e,
|
|
task,
|
|
},
|
|
`Task [${task.name()}] failed: ${serializeError(e)}`,
|
|
);
|
|
} finally {
|
|
await task.asyncDispose();
|
|
span.end();
|
|
}
|
|
});
|
|
};
|
|
|
|
// Entry point
|
|
const argv = asTaskArgs(minimist(process.argv.slice(2)));
|
|
// WARNING: the cron job will be enabled only in development mode
|
|
if (!RUN_SCHEDULED_TASKS_EXTERNALLY) {
|
|
logger.info('In-app cron job is enabled');
|
|
// run cron job every 1 minute
|
|
const job = CronJob.from({
|
|
cronTime: '0 * * * * *',
|
|
waitForCompletion: true,
|
|
onTick: async () => main(argv),
|
|
errorHandler: async err => {
|
|
console.error(err);
|
|
},
|
|
start: true,
|
|
timeZone: 'UTC',
|
|
});
|
|
} else {
|
|
logger.warn('In-app cron job is disabled');
|
|
main(argv)
|
|
.then(() => {
|
|
process.exit(0);
|
|
})
|
|
.catch(err => {
|
|
console.log(err);
|
|
logger.error({ err: serializeError(err) }, 'Task execution failed');
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
process.on('uncaughtException', (err: Error) => {
|
|
console.log(err);
|
|
logger.error({ err: serializeError(err) }, 'Uncaught exception');
|
|
process.exit(1);
|
|
});
|
|
|
|
process.on('unhandledRejection', (err: any) => {
|
|
console.log(err);
|
|
logger.error({ err: serializeError(err) }, 'Unhandled rejection');
|
|
process.exit(1);
|
|
});
|