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: https://github.com/hyperdxio/hyperdx/issues/1035
This commit is contained in:
Warren 2025-10-15 10:15:46 -07:00 committed by GitHub
parent 43e32aafc7
commit 348a4044e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 484 additions and 329 deletions

View file

@ -0,0 +1,6 @@
---
"@hyperdx/api": minor
"@hyperdx/app": minor
---
migration: migrate to Pino for standardized and faster logging

View file

@ -10,7 +10,7 @@
"@ai-sdk/anthropic": "^2.0.23",
"@esm2cjs/p-queue": "^7.3.0",
"@hyperdx/common-utils": "^0.7.0",
"@hyperdx/node-opentelemetry": "^0.8.2",
"@hyperdx/node-opentelemetry": "^0.9.0",
"@hyperdx/passport-local-mongoose": "^9.0.1",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/host-metrics": "^0.35.5",
@ -27,7 +27,6 @@
"express": "^4.19.2",
"express-rate-limit": "^6.7.1",
"express-session": "^1.17.3",
"express-winston": "^4.2.0",
"handlebars": "^4.7.8",
"http-graceful-shutdown": "^3.1.13",
"http-proxy-middleware": "^3.0.5",
@ -40,12 +39,13 @@
"on-headers": "^1.1.0",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"pino": "^10.0.0",
"pino-http": "^11.0.0",
"promised-handlebars": "^2.0.1",
"protobufjs": "^7.5.2",
"semver": "^7.5.2",
"serialize-error": "^8.1.0",
"uuid": "^8.3.2",
"winston": "^3.10.0",
"zod": "3.25",
"zod-express-middleware": "^1.4.0"
},
@ -67,6 +67,7 @@
"jest": "^28.1.3",
"migrate-mongo": "^11.0.0",
"nodemon": "^2.0.20",
"pino-pretty": "^13.1.1",
"rimraf": "^4.4.1",
"supertest": "^6.3.1",
"swagger-jsdoc": "^6.2.8",

View file

@ -63,10 +63,7 @@ const healthCheck = async () => {
const client = await getClickhouseClient();
const result = await client.ping();
if (!result.success) {
logger.error({
message: 'ClickHouse health check failed',
error: result.error,
});
logger.error({ error: result.error }, 'ClickHouse health check failed');
throw result.error;
}
};

View file

@ -21,7 +21,7 @@ if (config.IS_DEV) {
const server = new Server();
process.on('uncaughtException', (err: Error) => {
logger.error(serializeError(err));
logger.error({ err: serializeError(err) }, 'Uncaught exception');
// FIXME: disable server restart until
// we make sure all expected exceptions are handled properly
@ -32,7 +32,9 @@ process.on('uncaughtException', (err: Error) => {
process.on('unhandledRejection', (err: any) => {
// TODO: do we want to throw here ?
logger.error(serializeError(err));
logger.error({ err: serializeError(err) }, 'Unhandled rejection');
});
server.start().catch(e => logger.error(serializeError(e)));
server
.start()
.catch(e => logger.error({ err: serializeError(e) }, 'Server start failed'));

View file

@ -31,7 +31,8 @@ export function redirectToDashboard(req: Request, res: Response) {
return res.redirect(`${config.FRONTEND_URL}/search`);
} else {
logger.error(
`Password login for user failed, user or team not found ${req?.user?._id}`,
{ userId: req?.user?._id },
'Password login for user failed, user or team not found',
);
res.redirect(`${config.FRONTEND_URL}/login?err=unknown`);
}
@ -43,7 +44,7 @@ export function handleAuthError(
res: Response,
next: NextFunction,
) {
logger.debug({ message: 'Auth error', authErr: serializeError(err) });
logger.debug({ authErr: serializeError(err) }, 'Auth error');
if (res.headersSent) {
return next(err);
}

View file

@ -22,12 +22,12 @@ mongoose.connection.on('disconnected', () => {
logger.info('Lost connection to MongoDB server');
});
mongoose.connection.on('error', () => {
logger.error('Could not connect to MongoDB');
mongoose.connection.on('error', err => {
logger.error({ err }, 'Could not connect to MongoDB');
});
mongoose.connection.on('reconnected', () => {
logger.error('Reconnected to MongoDB');
logger.warn('Reconnected to MongoDB');
});
mongoose.connection.on('reconnectFailed', () => {

View file

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

View file

@ -63,7 +63,7 @@ export class AgentService {
return agent;
} catch (error) {
logger.error('Error processing agent status:', error);
logger.error({ err: error }, 'Error processing agent status');
throw error;
}
}

View file

@ -21,7 +21,7 @@ try {
root = protobuf.loadSync(PROTO_PATH);
logger.debug('OpAMP proto definition loaded successfully');
} catch (error) {
logger.error('Failed to load OpAMP proto definition:', error);
logger.error({ err: error }, 'Failed to load OpAMP proto definition');
throw error;
}
@ -46,7 +46,7 @@ export function decodeAgentToServer(data: Buffer): protobuf.Message {
try {
return AgentToServer.decode(data);
} catch (error) {
logger.error('Failed to decode AgentToServer message:', error);
logger.error({ err: error }, 'Failed to decode AgentToServer message');
throw error;
}
}
@ -68,7 +68,7 @@ export function encodeServerToAgent(message: any): Buffer {
// Encode the message
return Buffer.from(ServerToAgent.encode(serverToAgent).finish());
} catch (error) {
logger.error('Failed to encode ServerToAgent message:', error);
logger.error({ err: error }, 'Failed to encode ServerToAgent message');
throw error;
}
}
@ -105,7 +105,7 @@ export function createRemoteConfig(
configHash: configHash,
};
} catch (error) {
logger.error('Failed to create remote config message:', error);
logger.error({ err: error }, 'Failed to create remote config message');
throw error;
}
}
@ -130,7 +130,7 @@ function calculateConfigHash(configFiles: Map<string, Buffer>): Buffer {
return hash.digest();
} catch (error) {
logger.error('Failed to calculate config hash:', error);
logger.error({ err: error }, 'Failed to calculate config hash');
throw error;
}
}

View file

@ -188,7 +188,7 @@ router.post(
const source = await getSource(teamId.toString(), sourceId);
if (source == null) {
logger.error({ message: 'invalid source id', sourceId, teamId });
logger.error({ sourceId, teamId }, 'invalid source id');
return res.status(400).json({
error: 'Invalid source',
});

View file

@ -90,7 +90,10 @@ router.post(
password,
async (err: Error, user: any) => {
if (err) {
logger.error(serializeError(err));
logger.error(
{ err: serializeError(err) },
'User registration error',
);
return res.status(400).json({ error: 'invalid' });
}
@ -107,7 +110,8 @@ router.post(
await setupTeamDefaults(team._id.toString());
} catch (error) {
logger.error(
`Failed to setup team defaults: ${serializeError(error)}`,
{ err: serializeError(error) },
'Failed to setup team defaults',
);
// Continue with registration even if setup defaults fails
}
@ -118,7 +122,8 @@ router.post(
}
logger.error(
`Password login for user failed, user or team not found ${req?.user?._id}`,
{ userId: req?.user?._id },
'Password login for user failed, user or team not found',
);
return res.status(400).json({ error: 'invalid' });
});
@ -167,7 +172,7 @@ router.post('/team/setup/:token', async (req, res, next) => {
password, // TODO: validate password
async (err: Error, user: any) => {
if (err) {
logger.error(serializeError(err));
logger.error({ err: serializeError(err) }, 'Team setup error');
return res.redirect(
`${config.FRONTEND_URL}/join-team?token=${token}&err=500`,
);
@ -196,7 +201,7 @@ router.get('/ext/silence-alert/:token', async (req, res) => {
await silenceAlertByToken(token);
} catch (e) {
isError = true;
logger.error(e);
logger.error({ err: e }, 'Failed to silence alert');
}
// TODO: Create a template for utility pages

View file

@ -31,7 +31,10 @@ export default class Server {
if (mongoCloseResult.status === 'rejected') {
hasError = true;
logger.error(serializeError(mongoCloseResult.reason));
logger.error(
{ err: serializeError(mongoCloseResult.reason) },
'MongoDB client close failed',
);
} else {
logger.info('MongoDB client closed.');
}

View file

@ -36,7 +36,7 @@ export async function setupTeamDefaults(teamId: string) {
// Get the team object
const team = await getTeam(teamId);
if (!team) {
logger.warn(`Team not found with ID: ${teamId}`);
logger.warn({ teamId }, 'Team not found');
return;
}
@ -71,7 +71,7 @@ export async function setupTeamDefaults(teamId: string) {
`Created default connection: ${connectionConfig.name} (${newConnection._id})`,
);
} catch (error) {
logger.error(`Failed to create connection: ${error}`);
logger.error({ err: error }, 'Failed to create connection');
}
}
} else if (parsedDefaultConnections) {
@ -136,7 +136,7 @@ export async function setupTeamDefaults(teamId: string) {
c => c.name === connectionId,
);
if (!connection) {
logger.warn(`Connection not found with name: ${connectionId}`);
logger.warn({ connectionId }, 'Connection not found with name');
continue;
}
connectionId = connection._id.toString();
@ -166,7 +166,7 @@ export async function setupTeamDefaults(teamId: string) {
// Store the created source for the second pass
createdSources[sourceConfig.name] = newSource;
} catch (error) {
logger.error(`Failed to create source: ${error}`);
logger.error({ err: error }, 'Failed to create source');
}
}
@ -227,7 +227,7 @@ export async function setupTeamDefaults(teamId: string) {
);
}
} catch (error) {
logger.error(`Failed to update source references: ${error}`);
logger.error({ err: error }, 'Failed to update source references');
}
}
} else if (parsedDefaultSources) {

View file

@ -92,11 +92,13 @@ const fireChannelEvent = async ({
}
if ((alert.silenced?.until?.getTime() ?? 0) > Date.now()) {
logger.info({
message: 'Skipped firing alert due to silence',
alertId: alert.id,
silenced: alert.silenced,
});
logger.info(
{
alertId: alert.id,
silenced: alert.silenced,
},
'Skipped firing alert due to silence',
);
return;
}
@ -157,14 +159,16 @@ export const processAlert = async (
previous &&
fns.getTime(previous.createdAt) === fns.getTime(nowInMinsRoundDown)
) {
logger.info({
message: `Skipped to check alert since the time diff is still less than 1 window size`,
windowSizeInMins,
nowInMinsRoundDown,
previous,
now,
alertId: alert.id,
});
logger.info(
{
windowSizeInMins,
nowInMinsRoundDown,
previous,
now,
alertId: alert.id,
},
`Skipped to check alert since the time diff is still less than 1 window size`,
);
return;
}
const checkStartTime = previous
@ -218,20 +222,24 @@ export const processAlert = async (
};
}
} else {
logger.error({
message: `Unsupported alert source: ${alert.source}`,
alertId: alert.id,
});
logger.error(
{
alertId: alert.id,
},
`Unsupported alert source: ${alert.source}`,
);
return;
}
// Fetch data
if (chartConfig == null) {
logger.error({
message: 'Failed to build chart config',
chartConfig,
alertId: alert.id,
});
logger.error(
{
chartConfig,
alertId: alert.id,
},
'Failed to build chart config',
);
return;
}
@ -241,13 +249,15 @@ export const processAlert = async (
metadata,
});
logger.info({
message: `Received alert metric [${alert.source} source]`,
alertId: alert.id,
checksData,
checkStartTime,
checkEndTime,
});
logger.info(
{
alertId: alert.id,
checksData,
checkStartTime,
checkEndTime,
},
`Received alert metric [${alert.source} source]`,
);
// TODO: support INSUFFICIENT_DATA state
const history: IAlertHistory = {
@ -276,19 +286,23 @@ export const processAlert = async (
);
if (timestampColumnName == null) {
logger.error({
message: 'Failed to find timestamp column',
meta,
alertId: alert.id,
});
logger.error(
{
meta,
alertId: alert.id,
},
'Failed to find timestamp column',
);
return;
}
if (valueColumnNames.size === 0) {
logger.error({
message: 'Failed to find value column',
meta,
alertId: alert.id,
});
logger.error(
{
meta,
alertId: alert.id,
},
'Failed to find value column',
);
return;
}

View file

@ -34,11 +34,13 @@ const main = async (argv: TaskArgs) => {
`Task [${task.name()}] finished in ${(performance.now() - t0).toFixed(2)} ms`,
);
} catch (e: unknown) {
logger.error({
message: `Task [${task.name()}] failed: ${serializeError(e)}`,
cause: e,
task,
});
logger.error(
{
cause: e,
task,
},
`Task [${task.name()}] failed: ${serializeError(e)}`,
);
} finally {
await task.asyncDispose();
span.end();
@ -70,19 +72,19 @@ if (!RUN_SCHEDULED_TASKS_EXTERNALLY) {
})
.catch(err => {
console.log(err);
logger.error(serializeError(err));
logger.error({ err: serializeError(err) }, 'Task execution failed');
process.exit(1);
});
}
process.on('uncaughtException', (err: Error) => {
console.log(err);
logger.error(serializeError(err));
logger.error({ err: serializeError(err) }, 'Uncaught exception');
process.exit(1);
});
process.on('unhandledRejection', (err: any) => {
console.log(err);
logger.error(serializeError(err));
logger.error({ err: serializeError(err) }, 'Unhandled rejection');
process.exit(1);
});

View file

@ -102,11 +102,13 @@ export async function loadProvider(
try {
return providerFn();
} catch (err) {
logger.error({
message: `error creating instance of ${providerName} provider; using default`,
cause: err,
providerName,
});
logger.error(
{
cause: err,
providerName,
},
`error creating instance of ${providerName} provider; using default`,
);
}
}
}

View file

@ -106,15 +106,17 @@ function validateWebhookUrl(
// check that hostname ends in "slack.com"
if (!isValidSlackUrl(webhook.url)) {
const message = `Slack Webhook URL ${webhook.url} does not have hostname that ends in 'slack.com'`;
logger.warn({
webhook: {
id: webhook._id.toString(),
name: webhook.name,
url: webhook.url,
body: webhook.body,
logger.warn(
{
webhook: {
id: webhook._id.toString(),
name: webhook.name,
url: webhook.url,
body: webhook.body,
},
},
message,
});
);
throw new Error(`SSRF AllowedDomainError: ${message}`);
}
} else {
@ -124,15 +126,17 @@ function validateWebhookUrl(
const message = `Webhook attempting to query blacklisted route ${blacklistedWebhookHosts.get(
url.host,
)}`;
logger.warn({
webhook: {
id: webhook._id.toString(),
name: webhook.name,
url: webhook.url,
body: webhook.body,
logger.warn(
{
webhook: {
id: webhook._id.toString(),
name: webhook.name,
url: webhook.url,
body: webhook.body,
},
},
message,
});
);
throw new Error(`SSRF AllowedDomainError: ${message}`);
}
}
@ -200,10 +204,12 @@ export const handleSendGenericWebhook = async (
title: escapeJsonString(message.title),
});
} catch (e) {
logger.error({
message: 'Failed to compile generic webhook body',
error: serializeError(e),
});
logger.error(
{
error: serializeError(e),
},
'Failed to compile generic webhook body',
);
return;
}
@ -220,10 +226,12 @@ export const handleSendGenericWebhook = async (
throw new Error(errorText);
}
} catch (e) {
logger.error({
message: 'Failed to send generic webhook message',
error: serializeError(e),
});
logger.error(
{
error: serializeError(e),
},
'Failed to send generic webhook message',
);
}
};
@ -347,15 +355,18 @@ const getPopulatedChannel = (
findWebhookByName(channelIdOrNamePrefix, teamWebhooksById);
if (!webhook) {
logger.error('webhook not found', {
webhookId: channelIdOrNamePrefix,
});
logger.error(
{
webhookId: channelIdOrNamePrefix,
},
'webhook not found',
);
return undefined;
}
return { type: 'webhook', channel: webhook };
}
default: {
logger.error(`unsupported alert channel type: ${channelType}`);
logger.error({ channelType }, 'Unsupported alert channel type');
return undefined;
}
}
@ -505,12 +516,14 @@ export const renderAlertTemplate = async ({
2500,
);
} catch (e) {
logger.error({
message: 'Failed to fetch sample logs',
savedSearchId: savedSearch.id,
chartConfig,
error: serializeError(e),
});
logger.error(
{
savedSearchId: savedSearch.id,
chartConfig,
error: serializeError(e),
},
'Failed to fetch sample logs',
);
}
rawTemplateBody = `${group ? `Group: "${group}"` : ''}

View file

@ -4,7 +4,7 @@ import { MetricsDataType, SourceKind } from '@hyperdx/common-utils/dist/types';
import * as HyperDX from '@hyperdx/node-opentelemetry';
import ms from 'ms';
import os from 'os';
import winston from 'winston';
import pino from 'pino';
import * as config from '@/config';
import Connection from '@/models/connection';
@ -12,19 +12,19 @@ import { Source, SourceDocument } from '@/models/source';
import Team from '@/models/team';
import User from '@/models/user';
const logger = winston.createLogger({
const logger = pino({
level: 'info',
format: winston.format.json(),
transports: [
HyperDX.getWinstonTransport('info', {
headers: {
Authorization: '3f26ffad-14cf-4fb7-9dc9-e64fa0b84ee0', // hyperdx usage stats service api key
},
baseUrl: 'https://in-otel.hyperdx.io/v1/logs',
maxLevel: 'info',
service: 'hyperdx-oss-usage-stats',
}),
],
transport: {
targets: [
HyperDX.getPinoTransport('info', {
headers: {
Authorization: '3f26ffad-14cf-4fb7-9dc9-e64fa0b84ee0', // hyperdx usage stats service api key
},
baseUrl: 'https://in-otel.hyperdx.io/v1/logs',
service: 'hyperdx-oss-usage-stats',
}),
],
},
});
function extractTableNames(source: SourceDocument): string[] {
@ -128,37 +128,39 @@ async function getUsageStats() {
getClickhouseTableSize(),
]);
const clusterId = team[0]?._id.toString();
logger.info({
message: 'track-hyperdx-oss-usage-stats',
clusterId,
version: config.CODE_VERSION,
userCounts,
os: {
arch: os.arch(),
freemem: os.freemem(),
uptime: os.uptime(),
logger.info(
{
clusterId,
version: config.CODE_VERSION,
userCounts,
os: {
arch: os.arch(),
freemem: os.freemem(),
uptime: os.uptime(),
},
chStats: {
tables: chTables.reduce(
(acc, curr) => ({
...acc,
[curr.table]: {
avgDaySize: parseInt(curr.avgDaySize),
days: parseInt(curr.days),
lastModified: new Date(curr.latestModification).getTime(),
maxTime: new Date(curr.max_time).getTime(),
minTime: new Date(curr.min_time).getTime(),
rows: parseInt(curr.rows),
size: parseInt(curr.size),
},
}),
{},
),
rows: chTables.reduce((acc, curr) => acc + parseInt(curr.rows), 0),
size: chTables.reduce((acc, curr) => acc + parseInt(curr.size), 0),
},
timestamp: nowInMs,
},
chStats: {
tables: chTables.reduce(
(acc, curr) => ({
...acc,
[curr.table]: {
avgDaySize: parseInt(curr.avgDaySize),
days: parseInt(curr.days),
lastModified: new Date(curr.latestModification).getTime(),
maxTime: new Date(curr.max_time).getTime(),
minTime: new Date(curr.min_time).getTime(),
rows: parseInt(curr.rows),
size: parseInt(curr.size),
},
}),
{},
),
rows: chTables.reduce((acc, curr) => acc + parseInt(curr.rows), 0),
size: chTables.reduce((acc, curr) => acc + parseInt(curr.size), 0),
},
timestamp: nowInMs,
});
'track-hyperdx-oss-usage-stats',
);
} catch (err) {
// ignore
}

View file

@ -1,57 +1,100 @@
import { getWinstonTransport } from '@hyperdx/node-opentelemetry';
import expressWinston from 'express-winston';
import winston, { addColors } from 'winston';
import {
APP_TYPE,
HYPERDX_API_KEY,
HYPERDX_LOG_LEVEL,
IS_PROD,
} from '@/config';
getPinoMixinFunction,
getPinoTransport,
} from '@hyperdx/node-opentelemetry';
import type { Request, Response } from 'express';
import pino from 'pino';
import pinoHttp from 'pino-http';
// LOCAL DEV ONLY
addColors({
error: 'bold red',
warn: 'bold yellow',
info: 'white',
http: 'gray',
verbose: 'bold magenta',
debug: 'green',
silly: 'cyan',
});
import * as config from '@/config';
const MAX_LEVEL = HYPERDX_LOG_LEVEL ?? 'debug';
const DEFAULT_FORMAT = winston.format.combine(
winston.format.errors({ stack: true }),
winston.format.json(),
);
const MAX_LEVEL = config.HYPERDX_LOG_LEVEL ?? 'debug';
const hyperdxTransport = HYPERDX_API_KEY
? getWinstonTransport(MAX_LEVEL, {
bufferSize: APP_TYPE === 'scheduled-task' ? 1 : 100,
const hyperdxTransport = config.HYPERDX_API_KEY
? getPinoTransport(MAX_LEVEL, {
detectResources: true,
})
: null;
export const expressLogger = expressWinston.logger({
// Configure transport based on environment and whether HyperDX is enabled
const getTransport = () => {
const targets: any[] = [];
// Add HyperDX transport if API key is configured
if (hyperdxTransport) {
targets.push(hyperdxTransport);
}
if (config.IS_DEV || config.IS_CI) {
// In development, use pino-pretty for nice console output
targets.push({
target: 'pino-pretty',
level: MAX_LEVEL,
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname,trace_id,span_id,trace_flags',
},
});
} else {
targets.push({
target: 'pino/file',
level: MAX_LEVEL,
options: { destination: 1 }, // this writes to STDOUT
});
}
// If only one target, return it directly; otherwise return multi-transport
if (targets.length === 0) {
return undefined;
} else if (targets.length === 1) {
return targets[0];
} else {
return { targets };
}
};
const logger = pino({
level: MAX_LEVEL,
format: DEFAULT_FORMAT,
msg: IS_PROD
? undefined
: 'HTTP {{res.statusCode}} {{req.method}} {{req.url}} {{res.responseTime}}ms',
transports: [
new winston.transports.Console(),
...(hyperdxTransport ? [hyperdxTransport] : []),
],
meta: IS_PROD,
transport: getTransport(),
mixin: getPinoMixinFunction,
});
const logger = winston.createLogger({
level: MAX_LEVEL,
format: DEFAULT_FORMAT,
transports: [
new winston.transports.Console(),
...(hyperdxTransport ? [hyperdxTransport] : []),
],
export const expressLogger = pinoHttp({
logger,
customLogLevel: (_req, res, err) => {
if (res.statusCode >= 400 && res.statusCode < 500) {
return 'warn';
} else if (res.statusCode >= 500 || err) {
return 'error';
}
return 'info';
},
customSuccessMessage: (req: Request, res: Response) => {
return `HTTP ${req.method} ${req.originalUrl}`;
},
customErrorMessage: (req: Request, res: Response, err) => {
return `HTTP ${req.method} ${req.originalUrl}`;
},
customProps: (req: Request, res: Response) => {
const user = req.user;
if (user) {
return {
userId: user._id?.toString(),
userEmail: user.email,
};
}
return {};
},
// Only disable req/res serializers in development/CI
...(config.IS_DEV || config.IS_CI
? {
serializers: {
req: () => undefined,
res: () => undefined,
},
}
: {}),
});
export default logger;

View file

@ -45,7 +45,7 @@ passport.use(
}
return done(null, user, error);
} catch (err) {
logger.error(`Login for "${username}" failed, error: ${err}"`);
logger.error({ err, username }, 'Login failed with error');
return done(err);
}
},

View file

@ -30,7 +30,7 @@
"@hookform/resolvers": "^3.9.0",
"@hyperdx/browser": "^0.21.1",
"@hyperdx/common-utils": "^0.7.0",
"@hyperdx/node-opentelemetry": "^0.8.2",
"@hyperdx/node-opentelemetry": "^0.9.0",
"@lezer/highlight": "^1.2.0",
"@mantine/core": "7.9.2",
"@mantine/dates": "^7.11.2",

306
yarn.lock
View file

@ -3402,17 +3402,6 @@ __metadata:
languageName: node
linkType: hard
"@dabh/diagnostics@npm:^2.0.2":
version: 2.0.3
resolution: "@dabh/diagnostics@npm:2.0.3"
dependencies:
colorspace: "npm:1.1.x"
enabled: "npm:2.0.x"
kuler: "npm:^2.0.0"
checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe
languageName: node
linkType: hard
"@discoveryjs/json-ext@npm:^0.5.3":
version: 0.5.7
resolution: "@discoveryjs/json-ext@npm:0.5.7"
@ -4496,7 +4485,7 @@ __metadata:
"@ai-sdk/anthropic": "npm:^2.0.23"
"@esm2cjs/p-queue": "npm:^7.3.0"
"@hyperdx/common-utils": "npm:^0.7.0"
"@hyperdx/node-opentelemetry": "npm:^0.8.2"
"@hyperdx/node-opentelemetry": "npm:^0.9.0"
"@hyperdx/passport-local-mongoose": "npm:^9.0.1"
"@opentelemetry/api": "npm:^1.8.0"
"@opentelemetry/host-metrics": "npm:^0.35.5"
@ -4527,7 +4516,6 @@ __metadata:
express: "npm:^4.19.2"
express-rate-limit: "npm:^6.7.1"
express-session: "npm:^1.17.3"
express-winston: "npm:^4.2.0"
handlebars: "npm:^4.7.8"
http-graceful-shutdown: "npm:^3.1.13"
http-proxy-middleware: "npm:^3.0.5"
@ -4543,6 +4531,9 @@ __metadata:
on-headers: "npm:^1.1.0"
passport: "npm:^0.6.0"
passport-local: "npm:^1.0.0"
pino: "npm:^10.0.0"
pino-http: "npm:^11.0.0"
pino-pretty: "npm:^13.1.1"
promised-handlebars: "npm:^2.0.1"
protobufjs: "npm:^7.5.2"
rimraf: "npm:^4.4.1"
@ -4557,7 +4548,6 @@ __metadata:
tsconfig-paths: "npm:^4.2.0"
typescript: "npm:5.9.3"
uuid: "npm:^8.3.2"
winston: "npm:^3.10.0"
zod: "npm:3.25"
zod-express-middleware: "npm:^1.4.0"
languageName: unknown
@ -4574,7 +4564,7 @@ __metadata:
"@hookform/resolvers": "npm:^3.9.0"
"@hyperdx/browser": "npm:^0.21.1"
"@hyperdx/common-utils": "npm:^0.7.0"
"@hyperdx/node-opentelemetry": "npm:^0.8.2"
"@hyperdx/node-opentelemetry": "npm:^0.9.0"
"@jedmao/location": "npm:^3.0.0"
"@lezer/highlight": "npm:^1.2.0"
"@mantine/core": "npm:7.9.2"
@ -4786,9 +4776,9 @@ __metadata:
languageName: node
linkType: hard
"@hyperdx/node-opentelemetry@npm:^0.8.2":
version: 0.8.2
resolution: "@hyperdx/node-opentelemetry@npm:0.8.2"
"@hyperdx/node-opentelemetry@npm:^0.9.0":
version: 0.9.0
resolution: "@hyperdx/node-opentelemetry@npm:0.9.0"
dependencies:
"@hyperdx/instrumentation-exception": "npm:^0.1.0"
"@hyperdx/instrumentation-sentry-node": "npm:^0.1.0"
@ -4796,8 +4786,11 @@ __metadata:
"@opentelemetry/api-logs": "npm:^0.57.2"
"@opentelemetry/auto-instrumentations-node": "npm:^0.56.1"
"@opentelemetry/core": "npm:^1.30.1"
"@opentelemetry/exporter-logs-otlp-grpc": "npm:^0.57.2"
"@opentelemetry/exporter-logs-otlp-http": "npm:^0.57.2"
"@opentelemetry/exporter-metrics-otlp-grpc": "npm:^0.57.2"
"@opentelemetry/exporter-metrics-otlp-proto": "npm:^0.57.2"
"@opentelemetry/exporter-trace-otlp-grpc": "npm:^0.57.2"
"@opentelemetry/exporter-trace-otlp-proto": "npm:^0.57.2"
"@opentelemetry/instrumentation": "npm:^0.57.2"
"@opentelemetry/instrumentation-http": "npm:^0.57.2"
@ -4823,7 +4816,7 @@ __metadata:
winston-transport: "npm:^4.7.0"
bin:
opentelemetry-instrument: build/bin/opentelemetry-instrument.js
checksum: 10c0/9f723d78b3f0da6fb3fab38826385384f3aeeeb4e2852609151effac336627d006522bcc74618f5384b1ec778b488d5dcf51c992d013f9310e780bc7349e0975
checksum: 10c0/dc4101e78b1aca867377b68568a29ad2c1a12ae79ecd07486fbd152d1bc9d4b057469154cb49c7b4a8f6f690f9851d0cb497381bd7dd11ce11bca71e0f884981
languageName: node
linkType: hard
@ -6416,7 +6409,7 @@ __metadata:
languageName: node
linkType: hard
"@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.2":
"@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.2, @opentelemetry/exporter-logs-otlp-grpc@npm:^0.57.2":
version: 0.57.2
resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.57.2"
dependencies:
@ -6464,7 +6457,7 @@ __metadata:
languageName: node
linkType: hard
"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.2":
"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.2, @opentelemetry/exporter-metrics-otlp-grpc@npm:^0.57.2":
version: 0.57.2
resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.57.2"
dependencies:
@ -6526,7 +6519,7 @@ __metadata:
languageName: node
linkType: hard
"@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.2":
"@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.2, @opentelemetry/exporter-trace-otlp-grpc@npm:^0.57.2":
version: 0.57.2
resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.57.2"
dependencies:
@ -12127,6 +12120,13 @@ __metadata:
languageName: node
linkType: hard
"atomic-sleep@npm:^1.0.0":
version: 1.0.0
resolution: "atomic-sleep@npm:1.0.0"
checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a
languageName: node
linkType: hard
"attr-accept@npm:^2.2.4":
version: 2.2.5
resolution: "attr-accept@npm:2.2.5"
@ -13486,7 +13486,7 @@ __metadata:
languageName: node
linkType: hard
"color@npm:^3.1.3, color@npm:^3.2.1":
"color@npm:^3.2.1":
version: 3.2.1
resolution: "color@npm:3.2.1"
dependencies:
@ -13513,7 +13513,7 @@ __metadata:
languageName: node
linkType: hard
"colorette@npm:^2.0.10":
"colorette@npm:^2.0.10, colorette@npm:^2.0.7":
version: 2.0.20
resolution: "colorette@npm:2.0.20"
checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40
@ -13527,16 +13527,6 @@ __metadata:
languageName: node
linkType: hard
"colorspace@npm:1.1.x":
version: 1.1.4
resolution: "colorspace@npm:1.1.4"
dependencies:
color: "npm:^3.1.3"
text-hex: "npm:1.0.x"
checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123
languageName: node
linkType: hard
"combined-stream@npm:^1.0.8":
version: 1.0.8
resolution: "combined-stream@npm:1.0.8"
@ -14271,6 +14261,13 @@ __metadata:
languageName: node
linkType: hard
"dateformat@npm:^4.6.3":
version: 4.6.3
resolution: "dateformat@npm:4.6.3"
checksum: 10c0/e2023b905e8cfe2eb8444fb558562b524807a51cdfe712570f360f873271600b5c94aebffaf11efb285e2c072264a7cf243eadb68f3eba0f8cc85fb86cd25df6
languageName: node
linkType: hard
"dayjs@npm:^1.10.0":
version: 1.11.7
resolution: "dayjs@npm:1.11.7"
@ -14974,13 +14971,6 @@ __metadata:
languageName: node
linkType: hard
"enabled@npm:2.0.x":
version: 2.0.0
resolution: "enabled@npm:2.0.0"
checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f
languageName: node
linkType: hard
"encodeurl@npm:~1.0.2":
version: 1.0.2
resolution: "encodeurl@npm:1.0.2"
@ -16375,18 +16365,6 @@ __metadata:
languageName: node
linkType: hard
"express-winston@npm:^4.2.0":
version: 4.2.0
resolution: "express-winston@npm:4.2.0"
dependencies:
chalk: "npm:^2.4.2"
lodash: "npm:^4.17.21"
peerDependencies:
winston: ">=3.x <4"
checksum: 10c0/8f80993e7d7696b22a12c68ffb72ea0cd3ad980d8394073fd972162cdca116b8f506974dd504987e350cddfdbf55402871762dcb9c69f2f7feaef2df6d93ef09
languageName: node
linkType: hard
"express@npm:^4.17.3, express@npm:^4.19.2":
version: 4.19.2
resolution: "express@npm:4.19.2"
@ -16460,6 +16438,13 @@ __metadata:
languageName: node
linkType: hard
"fast-copy@npm:^3.0.2":
version: 3.0.2
resolution: "fast-copy@npm:3.0.2"
checksum: 10c0/02e8b9fd03c8c024d2987760ce126456a0e17470850b51e11a1c3254eed6832e4733ded2d93316c82bc0b36aeb991ad1ff48d1ba95effe7add7c3ab8d8eb554a
languageName: node
linkType: hard
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
version: 3.1.3
resolution: "fast-deep-equal@npm:3.1.3"
@ -16875,13 +16860,6 @@ __metadata:
languageName: node
linkType: hard
"fn.name@npm:1.x.x":
version: 1.1.0
resolution: "fn.name@npm:1.1.0"
checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a
languageName: node
linkType: hard
"focus-visible@npm:^5.2.0":
version: 5.2.0
resolution: "focus-visible@npm:5.2.0"
@ -17973,6 +17951,13 @@ __metadata:
languageName: node
linkType: hard
"help-me@npm:^5.0.0":
version: 5.0.0
resolution: "help-me@npm:5.0.0"
checksum: 10c0/054c0e2e9ae2231c85ab5e04f75109b9d068ffcc54e58fb22079822a5ace8ff3d02c66fd45379c902ad5ab825e5d2e1451fcc2f7eab1eb49e7d488133ba4cacb
languageName: node
linkType: hard
"hmac-drbg@npm:^1.0.1":
version: 1.0.1
resolution: "hmac-drbg@npm:1.0.1"
@ -20191,13 +20176,6 @@ __metadata:
languageName: node
linkType: hard
"kuler@npm:^2.0.0":
version: 2.0.0
resolution: "kuler@npm:2.0.0"
checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d
languageName: node
linkType: hard
"ky-universal@npm:^0.10.1":
version: 0.10.1
resolution: "ky-universal@npm:0.10.1"
@ -20564,7 +20542,7 @@ __metadata:
languageName: node
linkType: hard
"logform@npm:^2.3.2, logform@npm:^2.4.0":
"logform@npm:^2.3.2":
version: 2.5.1
resolution: "logform@npm:2.5.1"
dependencies:
@ -22925,6 +22903,13 @@ __metadata:
languageName: node
linkType: hard
"on-exit-leak-free@npm:^2.1.0":
version: 2.1.2
resolution: "on-exit-leak-free@npm:2.1.2"
checksum: 10c0/faea2e1c9d696ecee919026c32be8d6a633a7ac1240b3b87e944a380e8a11dc9c95c4a1f8fb0568de7ab8db3823e790f12bda45296b1d111e341aad3922a0570
languageName: node
linkType: hard
"on-finished@npm:2.4.1":
version: 2.4.1
resolution: "on-finished@npm:2.4.1"
@ -22957,15 +22942,6 @@ __metadata:
languageName: node
linkType: hard
"one-time@npm:^1.0.0":
version: 1.0.0
resolution: "one-time@npm:1.0.0"
dependencies:
fn.name: "npm:1.x.x"
checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa
languageName: node
linkType: hard
"onetime@npm:^5.1.0, onetime@npm:^5.1.2":
version: 5.1.2
resolution: "onetime@npm:5.1.2"
@ -23623,6 +23599,78 @@ __metadata:
languageName: node
linkType: hard
"pino-abstract-transport@npm:^2.0.0":
version: 2.0.0
resolution: "pino-abstract-transport@npm:2.0.0"
dependencies:
split2: "npm:^4.0.0"
checksum: 10c0/02c05b8f2ffce0d7c774c8e588f61e8b77de8ccb5f8125afd4a7325c9ea0e6af7fb78168999657712ae843e4462bb70ac550dfd6284f930ee57f17f486f25a9f
languageName: node
linkType: hard
"pino-http@npm:^11.0.0":
version: 11.0.0
resolution: "pino-http@npm:11.0.0"
dependencies:
get-caller-file: "npm:^2.0.5"
pino: "npm:^10.0.0"
pino-std-serializers: "npm:^7.0.0"
process-warning: "npm:^5.0.0"
checksum: 10c0/75110c7a7f1b1c4eadfbff3b87599ef9d100c20c3ffd19541f0cb37cd11285a3f221bb90d33df0772ed94a3f7d5b2007bd7365c5f5525cff2178d4deef140ee5
languageName: node
linkType: hard
"pino-pretty@npm:^13.1.1":
version: 13.1.1
resolution: "pino-pretty@npm:13.1.1"
dependencies:
colorette: "npm:^2.0.7"
dateformat: "npm:^4.6.3"
fast-copy: "npm:^3.0.2"
fast-safe-stringify: "npm:^2.1.1"
help-me: "npm:^5.0.0"
joycon: "npm:^3.1.1"
minimist: "npm:^1.2.6"
on-exit-leak-free: "npm:^2.1.0"
pino-abstract-transport: "npm:^2.0.0"
pump: "npm:^3.0.0"
secure-json-parse: "npm:^4.0.0"
sonic-boom: "npm:^4.0.1"
strip-json-comments: "npm:^5.0.2"
bin:
pino-pretty: bin.js
checksum: 10c0/845c07afd3d73cb96ad2049cfa7fca12b8280a51e30d6db8b490857690637556bb8e7f05b2fa640b3e4a7edd9b1369110042d670fda743ef98fe3be29876c8c7
languageName: node
linkType: hard
"pino-std-serializers@npm:^7.0.0":
version: 7.0.0
resolution: "pino-std-serializers@npm:7.0.0"
checksum: 10c0/73e694d542e8de94445a03a98396cf383306de41fd75ecc07085d57ed7a57896198508a0dec6eefad8d701044af21eb27253ccc352586a03cf0d4a0bd25b4133
languageName: node
linkType: hard
"pino@npm:^10.0.0":
version: 10.0.0
resolution: "pino@npm:10.0.0"
dependencies:
atomic-sleep: "npm:^1.0.0"
on-exit-leak-free: "npm:^2.1.0"
pino-abstract-transport: "npm:^2.0.0"
pino-std-serializers: "npm:^7.0.0"
process-warning: "npm:^5.0.0"
quick-format-unescaped: "npm:^4.0.3"
real-require: "npm:^0.2.0"
safe-stable-stringify: "npm:^2.3.1"
slow-redact: "npm:^0.3.0"
sonic-boom: "npm:^4.0.1"
thread-stream: "npm:^3.0.0"
bin:
pino: bin.js
checksum: 10c0/f95fcc51523310e9ece1822f8ef4d8e6c2b35f67eca9805fe18fdef21dfac81fa128f1ebaa3c9a11571120854391b10b3b339f2e5836f805edaf6936781c6e6f
languageName: node
linkType: hard
"pirates@npm:^4.0.1, pirates@npm:^4.0.6":
version: 4.0.6
resolution: "pirates@npm:4.0.6"
@ -24132,6 +24180,13 @@ __metadata:
languageName: node
linkType: hard
"process-warning@npm:^5.0.0":
version: 5.0.0
resolution: "process-warning@npm:5.0.0"
checksum: 10c0/941f48863d368ec161e0b5890ba0c6af94170078f3d6b5e915c19b36fb59edb0dc2f8e834d25e0d375a8bf368a49d490f080508842168832b93489d17843ec29
languageName: node
linkType: hard
"process@npm:^0.11.10":
version: 0.11.10
resolution: "process@npm:0.11.10"
@ -24450,6 +24505,13 @@ __metadata:
languageName: node
linkType: hard
"quick-format-unescaped@npm:^4.0.3":
version: 4.0.4
resolution: "quick-format-unescaped@npm:4.0.4"
checksum: 10c0/fe5acc6f775b172ca5b4373df26f7e4fd347975578199e7d74b2ae4077f0af05baa27d231de1e80e8f72d88275ccc6028568a7a8c9ee5e7368ace0e18eff93a4
languageName: node
linkType: hard
"quick-lru@npm:^4.0.1":
version: 4.0.1
resolution: "quick-lru@npm:4.0.1"
@ -25215,6 +25277,13 @@ __metadata:
languageName: node
linkType: hard
"real-require@npm:^0.2.0":
version: 0.2.0
resolution: "real-require@npm:0.2.0"
checksum: 10c0/23eea5623642f0477412ef8b91acd3969015a1501ed34992ada0e3af521d3c865bb2fe4cdbfec5fe4b505f6d1ef6a03e5c3652520837a8c3b53decff7e74b6a0
languageName: node
linkType: hard
"recast@npm:^0.23.3, recast@npm:^0.23.5":
version: 0.23.7
resolution: "recast@npm:0.23.7"
@ -26106,6 +26175,13 @@ __metadata:
languageName: node
linkType: hard
"secure-json-parse@npm:^4.0.0":
version: 4.1.0
resolution: "secure-json-parse@npm:4.1.0"
checksum: 10c0/52b3f8125ea974db1333a5b63e6a1df550c36c0d5f9a263911d6732812bd02e938b30be324dcbbb9da3ef9bf5a84849e0dd911f56544003d3c09e8eee12504de
languageName: node
linkType: hard
"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.7.1":
version: 5.7.1
resolution: "semver@npm:5.7.1"
@ -26558,6 +26634,13 @@ __metadata:
languageName: node
linkType: hard
"slow-redact@npm:^0.3.0":
version: 0.3.1
resolution: "slow-redact@npm:0.3.1"
checksum: 10c0/7f495a85108f554ea492b7fa1c8892b690a8172cb6d8cd77c56fef00a15d15f46b497a8eb61355a61cc06cbb34246088dc3372ed2ffb22e645c8c5dabc334563
languageName: node
linkType: hard
"smart-buffer@npm:^4.2.0":
version: 4.2.0
resolution: "smart-buffer@npm:4.2.0"
@ -26619,6 +26702,15 @@ __metadata:
languageName: node
linkType: hard
"sonic-boom@npm:^4.0.1":
version: 4.2.0
resolution: "sonic-boom@npm:4.2.0"
dependencies:
atomic-sleep: "npm:^1.0.0"
checksum: 10c0/ae897e6c2cd6d3cb7cdcf608bc182393b19c61c9413a85ce33ffd25891485589f39bece0db1de24381d0a38fc03d08c9862ded0c60f184f1b852f51f97af9684
languageName: node
linkType: hard
"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.2":
version: 1.0.2
resolution: "source-map-js@npm:1.0.2"
@ -26806,13 +26898,6 @@ __metadata:
languageName: node
linkType: hard
"stack-trace@npm:0.0.x":
version: 0.0.10
resolution: "stack-trace@npm:0.0.10"
checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b
languageName: node
linkType: hard
"stack-utils@npm:^2.0.3":
version: 2.0.6
resolution: "stack-utils@npm:2.0.6"
@ -27162,6 +27247,13 @@ __metadata:
languageName: node
linkType: hard
"strip-json-comments@npm:^5.0.2":
version: 5.0.3
resolution: "strip-json-comments@npm:5.0.3"
checksum: 10c0/daaf20b29f69fb51112698f4a9a662490dbb78d5baf6127c75a0a83c2ac6c078a8c0f74b389ad5e0519d6fc359c4a57cb9971b1ae201aef62ce45a13247791e0
languageName: node
linkType: hard
"strnum@npm:^1.0.5":
version: 1.0.5
resolution: "strnum@npm:1.0.5"
@ -27724,13 +27816,6 @@ __metadata:
languageName: node
linkType: hard
"text-hex@npm:1.0.x":
version: 1.0.0
resolution: "text-hex@npm:1.0.0"
checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d
languageName: node
linkType: hard
"text-table@npm:^0.2.0":
version: 0.2.0
resolution: "text-table@npm:0.2.0"
@ -27756,6 +27841,15 @@ __metadata:
languageName: node
linkType: hard
"thread-stream@npm:^3.0.0":
version: 3.1.0
resolution: "thread-stream@npm:3.1.0"
dependencies:
real-require: "npm:^0.2.0"
checksum: 10c0/c36118379940b77a6ef3e6f4d5dd31e97b8210c3f7b9a54eb8fe6358ab173f6d0acfaf69b9c3db024b948c0c5fd2a7df93e2e49151af02076b35ada3205ec9a6
languageName: node
linkType: hard
"through2@npm:^2.0.3":
version: 2.0.5
resolution: "through2@npm:2.0.5"
@ -29644,17 +29738,6 @@ __metadata:
languageName: node
linkType: hard
"winston-transport@npm:^4.5.0":
version: 4.5.0
resolution: "winston-transport@npm:4.5.0"
dependencies:
logform: "npm:^2.3.2"
readable-stream: "npm:^3.6.0"
triple-beam: "npm:^1.3.0"
checksum: 10c0/110a47c5acc87c3aa0f101741c0a992e52a86802272838c18aede8178d2b5e80254d2433dcac3439cefbc2777d9e22e65f84e9cee3130681c58e4ae5d58f50c3
languageName: node
linkType: hard
"winston-transport@npm:^4.7.0":
version: 4.7.0
resolution: "winston-transport@npm:4.7.0"
@ -29666,25 +29749,6 @@ __metadata:
languageName: node
linkType: hard
"winston@npm:^3.10.0":
version: 3.10.0
resolution: "winston@npm:3.10.0"
dependencies:
"@colors/colors": "npm:1.5.0"
"@dabh/diagnostics": "npm:^2.0.2"
async: "npm:^3.2.3"
is-stream: "npm:^2.0.0"
logform: "npm:^2.4.0"
one-time: "npm:^1.0.0"
readable-stream: "npm:^3.4.0"
safe-stable-stringify: "npm:^2.3.1"
stack-trace: "npm:0.0.x"
triple-beam: "npm:^1.3.0"
winston-transport: "npm:^4.5.0"
checksum: 10c0/eb9298ae9c335f54d4731525231df9f216d39a55bc1676b0375cbd9d2a14e302d0a700972ffb97626ab352e41f607739b5b8ee5feaadcf06c11977cb8ecd834a
languageName: node
linkType: hard
"word-wrap@npm:~1.2.3":
version: 1.2.5
resolution: "word-wrap@npm:1.2.5"