FastGPT/packages/service/common/system/log.ts
Archer 463b02d127
Some checks are pending
Document deploy / sync-images (push) Waiting to run
Document deploy / generate-timestamp (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.cn suffix:cn]) (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.io suffix:io]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.cn kube_config:KUBE_CONFIG_CN suffix:cn]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.io kube_config:KUBE_CONFIG_IO suffix:io]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / get-vars (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:amd64 runs-on:ubuntu-24.04]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:arm64 runs-on:ubuntu-24.04-arm]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / release-fastgpt-images (push) Blocked by required conditions
perf log (#6107)
* perf: html2md

* perf: index

* Add model log

* update next version

* log index
2025-12-17 17:44:38 +08:00

172 lines
4.8 KiB
TypeScript

import dayjs from 'dayjs';
import chalk from 'chalk';
import { LogLevelEnum } from './log/constant';
import { connectionMongo } from '../mongo/index';
import { getMongoLog } from './log/schema';
import { getLogger } from '../otel/log';
export enum EventTypeEnum {
outLinkBot = '[Outlink bot]',
feishuBot = '[Feishu bot]',
wxOffiaccount = '[Offiaccount bot]'
}
const logger = getLogger();
const logMap = {
[LogLevelEnum.debug]: {
levelLog: chalk.green('[Debug]')
},
[LogLevelEnum.info]: {
levelLog: chalk.blue('[Info]')
},
[LogLevelEnum.warn]: {
levelLog: chalk.yellow('[Warn]')
},
[LogLevelEnum.error]: {
levelLog: chalk.red('[Error]')
}
};
const envLogLevelMap: Record<string, number> = {
debug: LogLevelEnum.debug,
info: LogLevelEnum.info,
warn: LogLevelEnum.warn,
error: LogLevelEnum.error
};
const { LOG_LEVEL, STORE_LOG_LEVEL, SIGNOZ_STORE_LEVEL } = (() => {
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLocaleLowerCase();
const STORE_LOG_LEVEL = (process.env.STORE_LOG_LEVEL || '').toLocaleLowerCase();
const SIGNOZ_STORE_LEVEL = (process.env.SIGNOZ_STORE_LEVEL || 'warn').toLocaleLowerCase();
return {
LOG_LEVEL: envLogLevelMap[LOG_LEVEL] ?? LogLevelEnum.info,
STORE_LOG_LEVEL: envLogLevelMap[STORE_LOG_LEVEL] ?? 99,
SIGNOZ_STORE_LEVEL: envLogLevelMap[SIGNOZ_STORE_LEVEL] ?? LogLevelEnum.warn
};
})();
/**
* Sanitize object to prevent circular references for BSON serialization
* Remove properties that may contain circular references
*/
const sanitizeObjectForBSON = (obj: Record<string, any>): Record<string, any> => {
try {
// Use JSON stringify with replacer to handle circular references
const seen = new WeakSet();
const sanitized = JSON.parse(
JSON.stringify(obj, (key, value) => {
// Handle circular references
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular Reference]';
}
seen.add(value);
}
// Remove known problematic properties from axios config
if (key === 'config' && value && typeof value === 'object') {
return {
method: value.method,
url: value.url,
baseURL: value.baseURL,
headers: value.headers,
timeout: value.timeout,
responseType: value.responseType
};
}
// Remove functions and other non-serializable values
if (typeof value === 'function' || typeof value === 'symbol') {
return undefined;
}
return value;
})
);
return sanitized;
} catch (error) {
// If sanitization fails, return a safe fallback
return {
error: 'Failed to sanitize object',
originalKeys: Object.keys(obj)
};
}
};
/* add logger */
export const addLog = {
log(level: LogLevelEnum, msg: string, obj: Record<string, any> = {}) {
if (level < LOG_LEVEL) return;
const stringifyObj = JSON.stringify(obj);
const isEmpty = Object.keys(obj).length === 0;
console.log(
`${logMap[level].levelLog} ${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${msg} ${
level !== LogLevelEnum.error && !isEmpty ? stringifyObj : ''
}`
);
level === LogLevelEnum.error && console.error(obj);
if (logger && level >= SIGNOZ_STORE_LEVEL) {
logger.emit({
severityNumber: level.valueOf(),
severityText: ['debug', 'info', 'warn', 'error'][level],
body: {
msg,
obj
}
});
}
// store log
if (level >= STORE_LOG_LEVEL && connectionMongo.connection.readyState === 1) {
(async () => {
try {
// Sanitize metadata to prevent circular reference errors
const safeMetadata = sanitizeObjectForBSON(obj);
await getMongoLog().create({
text: msg,
level,
metadata: safeMetadata
});
} catch (error) {
console.error('store log error', error);
}
})();
}
},
debug(msg: string, obj?: Record<string, any>) {
this.log(LogLevelEnum.debug, msg, obj);
},
info(msg: string, obj?: Record<string, any>) {
this.log(LogLevelEnum.info, msg, obj);
},
warn(msg: string, obj?: Record<string, any>) {
this.log(LogLevelEnum.warn, msg, obj);
},
error(msg: string, error?: any) {
this.log(LogLevelEnum.error, msg, {
...(error?.data && { data: error?.data }),
message: error?.message || error,
stack: error?.stack,
...(error?.config && {
config: {
headers: error.config.headers,
url: error.config.url,
data: error.config.data
}
}),
...(error?.response && {
response: {
status: error.response.status,
statusText: error.response.statusText
}
})
});
}
};