feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
/*
* 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 { LogLevel, LogAction } from '../src/types';
import {
getColorByLogLevel,
ConsoleLogClient,
} from '../src/logger/console-client';
describe('console client test cases', () => {
test('getColorByLogLevel', () => {
expect(getColorByLogLevel(LogLevel.SUCCESS)).toBe('#00CC00');
expect(getColorByLogLevel(LogLevel.WARNING)).toBe('#CC9900');
expect(getColorByLogLevel(LogLevel.ERROR)).toBe('#CC3333');
expect(getColorByLogLevel(LogLevel.FATAL)).toBe('#FF0000');
expect(getColorByLogLevel(LogLevel.INFO)).toBe('#0099CC');
});
test('ConsoleLogClient', () => {
const client = new ConsoleLogClient();
const logSpy = vi.spyOn(console, 'log');
expect(
client.send({
meta: {},
}),
).toBeUndefined();
client.send({
meta: {},
action: [LogAction.CONSOLE],
message: 'test',
});
expect(logSpy).toHaveBeenCalled();
client.send({
action: [LogAction.CONSOLE],
eventName: 'test',
scope: 'test scope',
});
expect(logSpy).toHaveBeenCalledTimes(2);
});
});

View File

@@ -0,0 +1,81 @@
/*
* 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 { shouldCloseConsole } from '../src/console-disable';
describe('shouldCloseConsole', () => {
afterEach(() => {
vi.clearAllMocks();
vi.unstubAllGlobals();
});
test('URL search can control the result', () => {
vi.stubGlobal('sessionStorage', {
getItem: () => false,
setItem: vi.fn(),
});
vi.stubGlobal('IS_RELEASE_VERSION', true);
vi.stubGlobal('IS_PROD', true);
vi.stubGlobal('window', {
location: { search: '' },
gfdatav1: {
canary: 0,
},
});
expect(shouldCloseConsole()).equal(true);
vi.stubGlobal('window', { location: { search: '?open_debug=true' } });
expect(shouldCloseConsole()).equal(false);
vi.stubGlobal('window', {
location: { search: '?test=a&open_debug=true' },
});
expect(shouldCloseConsole()).equal(false);
vi.stubGlobal('sessionStorage', {
getItem: () => true,
setItem: vi.fn(),
});
vi.stubGlobal('window', { location: { search: '' } });
expect(shouldCloseConsole()).equal(false);
});
test('Production mode should return true', () => {
vi.stubGlobal('IS_PROD', true);
vi.stubGlobal('window', {
location: { search: '?test=a' },
gfdatav1: {
canary: 0,
},
});
vi.stubGlobal('IS_RELEASE_VERSION', false);
expect(shouldCloseConsole()).equal(false);
vi.stubGlobal('IS_RELEASE_VERSION', true);
expect(shouldCloseConsole()).equal(true);
vi.stubGlobal('IS_RELEASE_VERSION', false);
expect(shouldCloseConsole()).equal(false);
vi.stubGlobal('window', {
location: { search: '?test=a&&open_debug=true' },
gfdatav1: {
canary: 0,
},
});
vi.stubGlobal('IS_RELEASE_VERSION', true);
expect(shouldCloseConsole()).equal(false);
});
});

View File

@@ -0,0 +1,52 @@
/*
* 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 TraceDuration,
genDurationTracer,
} from '../src/reporter/duration-tracer';
// A constant interval just to test the tracer is valid
const CONSTANT_INTERVAL = 100;
vi.stubGlobal('performance', {
mark: vi.fn(),
measure: () => ({
duration: CONSTANT_INTERVAL,
}),
});
describe('duration-tracer', () => {
test('Does not collect empty pointName', () => {
const { tracer } = genDurationTracer();
const result = tracer('');
expect(result.points.length).equal(0);
});
test('Durations are collected correctly', () => {
const { tracer } = genDurationTracer();
tracer('step1');
const result1: TraceDuration = tracer('step2');
expect(result1.points).toStrictEqual(['step1', 'step2']);
expect(result1.interval.step2).equal(CONSTANT_INTERVAL);
const result2 = tracer('step3');
expect(result2.points).toStrictEqual(['step1', 'step2', 'step3']);
expect(result2.interval.step3).equal(CONSTANT_INTERVAL);
// Multiple pointName will be filtered
tracer('step3');
expect(result2.points).toStrictEqual(['step1', 'step2', 'step3']);
});
});

View File

@@ -0,0 +1,63 @@
/*
* 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 Mock } from 'vitest';
import { Logger } from '../src/logger/logger';
import { shouldCloseConsole } from '../src/console-disable';
import { type SlardarInstance } from '../src';
vi.mock('../src/console-disable');
vi.stubGlobal('IS_RELEASE_VERSION', undefined);
describe('logger', () => {
beforeEach(() => {
vi.clearAllMocks();
});
test('should create another instance correctly', () => {
const logger = new Logger();
const anotherLogger = logger.createLoggerWith({});
expect(anotherLogger).toBeInstanceOf(Logger);
});
test('should trigger disable-console when calling logger.xxx functions', () => {
const logger = new Logger();
logger.init({} as unknown as SlardarInstance);
['info', 'success', 'warning', 'error'].forEach(fnName => {
logger[fnName]({ message: 'test' });
expect(logger.disableConsole).toBe(false);
});
logger.setup({ 'no-console': true });
// create after setup should also inherit no-console
const logger2 = logger.createLoggerWith({});
['info', 'success', 'warning', 'error'].forEach(fnName => {
(shouldCloseConsole as Mock).mockReturnValue(true);
logger[fnName]({ message: 'test' });
logger2[fnName]({ message: 'test' });
expect(logger.disableConsole).toBe(true);
expect(logger2.disableConsole).toBe(true);
(shouldCloseConsole as Mock).mockReturnValue(false);
logger[fnName]({ message: 'test' });
logger2[fnName]({ message: 'test' });
expect(logger.disableConsole).toBe(false);
expect(logger2.disableConsole).toBe(false);
});
});
});

View File

@@ -0,0 +1,265 @@
/*
* 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 TraceDuration } from '../src/reporter/duration-tracer';
import { Reporter, reporter as rawReporter } from '../src/reporter';
vi.mock('../src/logger', () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
function Logger(config: Record<string, unknown>) {
return {
ctx: config.meta,
namespace: config.namespace,
scope: config.scope,
addClient: vi.fn(),
info: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
persist: {
info: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
addClient: vi.fn(),
},
};
}
return {
Logger,
};
});
vi.mock('../src/slardar', () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
function SlardarReportClient() {
return null;
}
return {
SlardarReportClient,
};
});
// A constant interval just to test the tracer is valid
const CONSTANT_INTERVAL = 100;
vi.stubGlobal('performance', {
mark: vi.fn(),
measure: () => ({
duration: CONSTANT_INTERVAL,
}),
});
describe('reporter', () => {
afterEach(() => {
vi.clearAllMocks();
});
test('With on slardar instance', () => {
const reporter = new Reporter({});
reporter.init(null);
// @ts-expect-error private member
expect(reporter.initialized).equal(false);
});
test('Should not call the logger function if `init` is not called, also the messages will be inserted into `pendingQueue`', () => {
const reporter = new Reporter({});
// @ts-expect-error private member
const logger = reporter.logger.persist;
reporter.success({ message: 'success' });
expect(logger.success).not.toHaveBeenCalled();
reporter.info({ message: 'info' });
expect(logger.info).not.toHaveBeenCalled();
reporter.warning({ message: 'warning' });
expect(logger.warning).not.toHaveBeenCalled();
reporter.error({ message: 'error', error: new Error() });
expect(logger.error).not.toHaveBeenCalled();
reporter.event({ eventName: 'e1' });
expect(logger.info).not.toHaveBeenCalled();
reporter.successEvent({ eventName: 's1' });
expect(logger.success).not.toHaveBeenCalled();
reporter.errorEvent({ eventName: 'e2', error: new Error() });
expect(logger.error).not.toHaveBeenCalled();
// @ts-expect-error private member
expect(reporter.pendingQueue.length).equal(7);
});
test('Should call logger function if init is called, also the `pendingQueue` should be empty', () => {
const reporter = new Reporter({});
reporter.init({} as any);
// @ts-expect-error private member
const logger = reporter.logger.persist;
reporter.success({ message: 'success' });
expect(logger.success).toHaveBeenCalled();
reporter.info({ message: 'info' });
expect(logger.info).toHaveBeenCalled();
reporter.warning({ message: 'warning' });
expect(logger.warning).toHaveBeenCalled();
reporter.error({ message: 'error', error: new Error() });
expect(logger.error).toHaveBeenCalled();
reporter.event({ eventName: 'e1' });
expect(logger.info).toHaveBeenCalled();
reporter.successEvent({ eventName: 's1' });
expect(logger.success).toHaveBeenCalled();
reporter.errorEvent({ eventName: 'e2', error: new Error() });
expect(logger.error).toHaveBeenCalled();
// @ts-expect-error private member
expect(reporter.pendingQueue.length).equal(0);
});
test('If `init` is called after then logger functions, the messages will be inserted into `pendingQueue` which will be handled and clear out when initialization is finished', async () => {
const reporter = new Reporter({});
// @ts-expect-error private member
const logger = reporter.logger.persist;
reporter.success({ message: 'success' });
expect(logger.success).not.toHaveBeenCalled();
reporter.info({ message: 'info' });
expect(logger.info).not.toHaveBeenCalled();
reporter.warning({ message: 'warning' });
expect(logger.warning).not.toHaveBeenCalled();
reporter.error({ message: 'error', error: new Error() });
expect(logger.error).not.toHaveBeenCalled();
reporter.event({ eventName: 'e1' });
expect(logger.info).not.toHaveBeenCalled();
reporter.errorEvent({ eventName: 'e2', error: new Error() });
expect(logger.error).not.toHaveBeenCalled();
reporter.successEvent({ eventName: 's1' });
expect(logger.success).not.toHaveBeenCalled();
// @ts-expect-error private member
expect(reporter.pendingQueue.length).equal(7);
const RANDOM_DURATION = 100;
await wait(RANDOM_DURATION);
reporter.init({} as any);
expect(logger.success).toHaveBeenCalled();
expect(logger.info).toHaveBeenCalled();
expect(logger.warning).toHaveBeenCalled();
expect(logger.error).toHaveBeenCalled();
// @ts-expect-error private member
expect(reporter.pendingQueue.length).equal(0);
});
test('createReporterWithPreset', () => {
const presetReporter = rawReporter.createReporterWithPreset({});
expect(presetReporter.getLogger()).not.undefined;
expect(presetReporter.slardarInstance).not.undefined;
});
describe('Error Event', () => {
test('The meta of the error event should contain error object', () => {
const reporter = new Reporter({});
reporter.errorEvent({
eventName: 'e',
error: new Error('custom_message'),
});
// @ts-expect-error private member
const queue = reporter.pendingQueue;
expect(queue.length).equal(1);
const item = queue[0];
expect(item.error).instanceOf(Error);
expect(item.meta.errorMessage).equal('custom_message');
});
});
describe('Success Event', () => {
test('The logger.success should be called', () => {
const reporter = new Reporter({});
reporter.init({} as any);
reporter.successEvent({
eventName: 'e',
});
// @ts-expect-error private member
const logger = reporter.logger.persist;
expect(logger.success).toHaveBeenCalled();
});
});
describe('Trace Event', () => {
test('No any trace should not call the logger function', () => {
const reporter = new Reporter({});
reporter.init({} as any);
// Generate but ot use the tracer
reporter.tracer({ eventName: 'e' });
// @ts-expect-error private member
const logger = reporter.logger.persist;
expect(logger.info).not.toHaveBeenCalled();
});
test('Multiple steps logger in order with correct duration', () => {
const reporter = new Reporter({});
const { trace } = reporter.tracer({
eventName: 'e',
});
trace('step1');
trace('step2');
trace('success');
// @ts-expect-error private member
const queue = reporter.pendingQueue;
expect(queue.length).equal(3);
const lastItem = queue[queue.length - 1];
const duration = lastItem.meta.duration as TraceDuration;
expect(duration.points).toStrictEqual(['step1', 'step2', 'success']);
expect(duration.interval.step2).equal(CONSTANT_INTERVAL);
expect(duration.interval.success).equal(CONSTANT_INTERVAL);
});
test('The meta of the error step should contain error object', () => {
const reporter = new Reporter({});
const { trace } = reporter.tracer({
eventName: 'e',
});
trace('fail', {
error: new Error(),
});
// @ts-expect-error private member
const queue = reporter.pendingQueue;
expect(queue.length).equal(1);
const item = queue[0];
expect(item.meta.error).instanceOf(Error);
});
test('The meta should be recorded correctly', () => {
const reporter = new Reporter({});
const { trace } = reporter.tracer({
eventName: 'e',
});
trace('step1', {
meta: {
m1: 1, // number
c1: 'any', // string
},
});
// @ts-expect-error private member
const queue = reporter.pendingQueue;
expect(queue.length).equal(1);
const item = queue[0];
expect(item.meta.m1).equal(1);
expect(item.meta.c1).equal('any');
});
});
});
async function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,87 @@
/*
* 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 { LogAction, LogLevel } from '../src/types';
import { SlardarReportClient } from '../src/slardar';
vi.mock('@slardar/web');
const captureException = vi.fn();
const sendEvent = vi.fn();
const sendLog = vi.fn();
const mockSlardarInstance = function (type) {
if (type === 'captureException') {
captureException();
}
if (type === 'sendEvent') {
sendEvent();
}
if (type === 'sendLog') {
sendLog();
}
};
describe('slardar reporter client test cases', () => {
afterEach(() => {
vi.clearAllMocks();
});
test('slardar init fail', () => {
const consoleSpy = vi.spyOn(console, 'warn');
new SlardarReportClient(null);
expect(consoleSpy).toHaveBeenCalled();
});
test('slardar just report persist log', () => {
const slardarReportClient = new SlardarReportClient(mockSlardarInstance);
expect(
slardarReportClient.send({
action: [LogAction.CONSOLE],
}),
).toBeUndefined();
});
test('slardar report error', () => {
const slardarReportClient = new SlardarReportClient(mockSlardarInstance);
slardarReportClient.send({
action: [LogAction.PERSIST],
level: LogLevel.ERROR,
meta: {
reportJsError: true,
},
});
expect(captureException).toHaveBeenCalled();
});
test('slardar report event', () => {
const slardarReportClient = new SlardarReportClient(mockSlardarInstance);
slardarReportClient.send({
action: [LogAction.PERSIST],
level: LogLevel.INFO,
eventName: 'test-event',
});
expect(sendEvent).toHaveBeenCalled();
});
test('slardar report log', () => {
const slardarReportClient = new SlardarReportClient(mockSlardarInstance);
slardarReportClient.send({
action: [LogAction.PERSIST],
level: LogLevel.INFO,
message: 'test message',
});
expect(sendLog).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,93 @@
/*
* 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 { ErrorType } from '../src/types';
import {
safeJson,
ApiError,
getErrorType,
getApiErrorRecord,
} from '../src/slardar/utils';
describe('slardar utils function is normal', () => {
test('safeJson stringify and parse success catch error', () => {
const mock = {
test: {},
};
mock.test = mock;
expect(safeJson.stringify(mock)).toContain('JSON stringify Error:');
expect(safeJson.parse('{')).toBeNull();
});
test('ApiError', () => {
const apiError = new ApiError({
httpStatus: '200',
code: '0',
message: 'test',
logId: '123',
});
expect(apiError.name).toBe('ApiError');
});
test('getErrorType', () => {
const errorType = getErrorType({
name: '',
message: '',
});
expect(errorType).toBe(ErrorType.Unknown);
const errorType1 = getErrorType(null);
expect(errorType1).toBe(ErrorType.Unknown);
const apiError = new ApiError({
httpStatus: '200',
code: '0',
message: 'test',
logId: '123',
});
const errorType2 = getErrorType(apiError);
expect(errorType2).toBe(ErrorType.ApiError);
const apiError2 = new ApiError({
httpStatus: '200',
code: '0',
message: 'test',
logId: '123',
errorType: 'test',
});
const errorType3 = getErrorType(apiError2);
expect(errorType3).toBe('test');
});
test('getApiErrorRecord', () => {
const error1 = getApiErrorRecord(null);
expect(error1).toEqual({});
const apiError = new ApiError({
httpStatus: '200',
code: '0',
message: 'test',
logId: '123',
response: {},
requestConfig: {},
});
const error2 = getApiErrorRecord(apiError);
expect(error2.response).toBe('{}');
});
});