feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type TracePointName = 'success' | 'fail' | string;
|
||||
|
||||
export interface TraceDuration {
|
||||
points: TracePointName[];
|
||||
interval: {
|
||||
[key: TracePointName]: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function genDurationTracer() {
|
||||
const duration: TraceDuration = {
|
||||
points: [],
|
||||
interval: {},
|
||||
};
|
||||
|
||||
const tracer = (pointName: TracePointName) => {
|
||||
if (!pointName) {
|
||||
return duration;
|
||||
}
|
||||
if (duration.points.indexOf(pointName) === -1) {
|
||||
duration.points.push(pointName);
|
||||
}
|
||||
performance.mark(pointName);
|
||||
if (duration.points.length > 1) {
|
||||
const curIdx = duration.points.length - 1;
|
||||
const measure = performance.measure(
|
||||
'measure',
|
||||
duration.points[curIdx - 1],
|
||||
duration.points[curIdx],
|
||||
);
|
||||
duration.interval[pointName] = measure?.duration ?? 0;
|
||||
}
|
||||
|
||||
return duration;
|
||||
};
|
||||
|
||||
return {
|
||||
tracer,
|
||||
};
|
||||
}
|
||||
320
frontend/packages/arch/logger/src/reporter/index.ts
Normal file
320
frontend/packages/arch/logger/src/reporter/index.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type CommonLogOptions, LogAction, LogLevel } from '../types';
|
||||
import { SlardarReportClient, type SlardarInstance } from '../slardar';
|
||||
import { Logger } from '../logger';
|
||||
import { genDurationTracer, type TracePointName } from './duration-tracer';
|
||||
|
||||
export interface LoggerCommonProperties {
|
||||
namespace?: string;
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
export interface SlardarMeta {
|
||||
meta?: Record<string, unknown>; // Combination of `categories` and `metrics`, check more:
|
||||
}
|
||||
|
||||
export interface CustomLog extends SlardarMeta, LoggerCommonProperties {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type CustomErrorLog = CustomLog & { error: Error };
|
||||
|
||||
export interface CustomEvent<EventEnum extends string>
|
||||
extends SlardarMeta,
|
||||
LoggerCommonProperties {
|
||||
eventName: EventEnum;
|
||||
}
|
||||
|
||||
export interface ErrorEvent<EventEnum extends string>
|
||||
extends CustomEvent<EventEnum> {
|
||||
error: Error;
|
||||
level?: 'error' | 'fatal';
|
||||
}
|
||||
|
||||
export interface TraceEvent<EventEnum extends string>
|
||||
extends LoggerCommonProperties {
|
||||
eventName: EventEnum;
|
||||
}
|
||||
|
||||
export interface TraceOptions extends SlardarMeta {
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
type ReporterConfig = LoggerCommonProperties & SlardarMeta;
|
||||
|
||||
type LogType = 'info' | 'success' | 'warning' | 'error';
|
||||
|
||||
export class Reporter {
|
||||
private initialized = false;
|
||||
private logger: Logger;
|
||||
private pendingQueue: CommonLogOptions[] = [];
|
||||
private pendingInstance: Reporter[] = [];
|
||||
public slardarInstance: SlardarInstance | null = null;
|
||||
|
||||
private log(type: LogType, payload: CommonLogOptions) {
|
||||
if (!this.check(payload)) {
|
||||
return;
|
||||
}
|
||||
this.logger.disableConsole = true;
|
||||
this.logger[type](payload as CommonLogOptions & { error: Error });
|
||||
this.logger.persist.disableConsole = true;
|
||||
this.logger.persist[type](payload as CommonLogOptions & { error: Error });
|
||||
}
|
||||
|
||||
constructor(config?: ReporterConfig) {
|
||||
this.logger = new Logger({
|
||||
clients: [],
|
||||
ctx: {
|
||||
...config,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个带有preset的reporter,一般可以配置专属的`namespace`和`scope`
|
||||
* @param preset
|
||||
* @returns
|
||||
*/
|
||||
createReporterWithPreset(preset: ReporterConfig) {
|
||||
const r = new Reporter(preset);
|
||||
if (this.initialized) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
r.init(this.slardarInstance!);
|
||||
} else {
|
||||
this.pendingInstance.push(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化reporter
|
||||
* @param slardarInstance 需要上报的slardar实例
|
||||
* @returns
|
||||
*/
|
||||
init(slardarInstance: SlardarInstance) {
|
||||
if (!slardarInstance) {
|
||||
console.warn('You should use reporter with a valid slardar instance');
|
||||
return;
|
||||
}
|
||||
const slardarReportClient = new SlardarReportClient(slardarInstance);
|
||||
this.slardarInstance = slardarReportClient.slardarInstance;
|
||||
this.logger.persist.addClient(slardarReportClient);
|
||||
this.initialized = true;
|
||||
|
||||
// Execute all pending items which are collected before initialization
|
||||
this.pendingQueue.forEach(item => {
|
||||
const levelFuncName: Omit<LogLevel, LogLevel.ERROR> =
|
||||
item.level || LogLevel.INFO;
|
||||
this.log(levelFuncName.toString() as LogType, item);
|
||||
});
|
||||
this.pendingQueue = [];
|
||||
|
||||
// Run init for all pending reporter instances
|
||||
this.pendingInstance.forEach(instance => {
|
||||
instance.init(slardarInstance);
|
||||
});
|
||||
this.pendingInstance = [];
|
||||
}
|
||||
|
||||
getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
/// Custom Log
|
||||
/**
|
||||
* 上报一个info日志
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
info(log: CustomLog) {
|
||||
this.log('info', log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报一个success日志
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
success(log: CustomLog) {
|
||||
const info = this.formatCustomLog(log, LogLevel.SUCCESS);
|
||||
this.log('success', info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报一个warning日志
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
warning(log: CustomLog) {
|
||||
const info = this.formatCustomLog(log, LogLevel.WARNING);
|
||||
this.log('warning', info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报一个error日志
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
error(log: CustomErrorLog) {
|
||||
const info = this.formatCustomLog(
|
||||
log,
|
||||
LogLevel.ERROR,
|
||||
) as CommonLogOptions & { error: Error };
|
||||
this.log('error', info);
|
||||
}
|
||||
|
||||
/// Custom Event
|
||||
/**
|
||||
* 上报一个自定义event事件
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
event<EventEnum extends string>(event: CustomEvent<EventEnum>) {
|
||||
const e = this.formatCustomEvent(event);
|
||||
this.log('info', e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报一个错误event事件(LogLevel = 'error')
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
errorEvent<EventEnum extends string>(event: ErrorEvent<EventEnum>) {
|
||||
const e = this.formatErrorEvent(event) as CommonLogOptions & {
|
||||
error: Error;
|
||||
};
|
||||
this.log('error', e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报一个成功event事件(LogLevel = 'success')
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
successEvent<EventEnum extends string>(event: CustomEvent<EventEnum>) {
|
||||
const e = this.formatCustomEvent(event) as CommonLogOptions;
|
||||
this.log('success', e);
|
||||
}
|
||||
|
||||
/// Trace Event
|
||||
/**
|
||||
* 性能追踪,可以记录一个流程中多个步骤间隔的耗时:
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
tracer<EventEnum extends string>({ eventName }: TraceEvent<EventEnum>) {
|
||||
const { tracer: durationTracer } = genDurationTracer();
|
||||
|
||||
const trace = (pointName: TracePointName, options: TraceOptions = {}) => {
|
||||
const { meta, error } = options;
|
||||
const e = this.formatCustomEvent({
|
||||
eventName,
|
||||
meta: {
|
||||
...meta,
|
||||
error,
|
||||
duration: durationTracer(pointName),
|
||||
},
|
||||
});
|
||||
if (!this.check(e)) {
|
||||
return;
|
||||
}
|
||||
this.log('info', e);
|
||||
};
|
||||
|
||||
return {
|
||||
trace,
|
||||
};
|
||||
}
|
||||
|
||||
private check(info: CommonLogOptions) {
|
||||
if (!this.initialized) {
|
||||
// Initialization has not been called, collect the item into queue and consume it when called.
|
||||
this.pendingQueue.push(info);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private formatCustomLog(
|
||||
log: CustomLog | CustomErrorLog,
|
||||
level: LogLevel,
|
||||
): CommonLogOptions {
|
||||
const {
|
||||
namespace: ctxNamespace,
|
||||
scope: ctxScope,
|
||||
meta: ctxMeta = {},
|
||||
} = this.logger.ctx?.options ?? {};
|
||||
const { namespace, scope, meta = {}, message } = log;
|
||||
return {
|
||||
action: [LogAction.CONSOLE, LogAction.PERSIST],
|
||||
namespace: namespace || ctxNamespace,
|
||||
scope: scope || ctxScope,
|
||||
level,
|
||||
error: (log as CustomErrorLog).error,
|
||||
|
||||
message,
|
||||
meta: {
|
||||
...ctxMeta,
|
||||
...meta,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private formatCustomEvent<EventEnum extends string>(
|
||||
event: CustomEvent<EventEnum>,
|
||||
): CommonLogOptions {
|
||||
const {
|
||||
namespace: ctxNamespace,
|
||||
scope: ctxScope,
|
||||
meta: ctxMeta = {},
|
||||
} = this.logger.ctx?.options ?? {};
|
||||
const { eventName, namespace, scope, meta = {} } = event;
|
||||
return {
|
||||
action: [LogAction.CONSOLE, LogAction.PERSIST],
|
||||
namespace: namespace || ctxNamespace,
|
||||
scope: scope || ctxScope,
|
||||
eventName,
|
||||
|
||||
meta: {
|
||||
...ctxMeta,
|
||||
...meta,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private formatErrorEvent<EventEnum extends string>(
|
||||
event: ErrorEvent<EventEnum>,
|
||||
): CommonLogOptions {
|
||||
const e = this.formatCustomEvent(event);
|
||||
return {
|
||||
...e,
|
||||
meta: {
|
||||
...e.meta,
|
||||
// !NOTE: Slardar不支持`a.b`的字段的正则搜索(会报错),需要把`error.message`和`error.name`铺平放到第一层
|
||||
errorMessage: event.error.message,
|
||||
errorName: event.error.name,
|
||||
level: event.level ?? 'error',
|
||||
},
|
||||
error: event.error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const reporter = new Reporter();
|
||||
|
||||
export { reporter };
|
||||
Reference in New Issue
Block a user