New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[KODO-12017] 添加生产模式输出详细日志功能及配置 #499
Changes from 9 commits
381a904
628894a
ee7022b
39b01b4
3d7983d
7adadc7
84575bc
738861c
bf0e92a
3334fbf
7187c4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.DS_Store | ||
.vscode | ||
node_modules | ||
bower_components | ||
demo/config.js | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,9 @@ | ||
import StatisticsLogger from './statisticsLog' | ||
import createUploadManager, { Extra, Config, UploadOptions, UploadProgress } from './upload' | ||
import { Observable, IObserver } from './observable' | ||
import { CustomError } from './utils' | ||
import { UploadCompleteData } from './api' | ||
import compressImage from './compress' | ||
|
||
const statisticsLogger = new StatisticsLogger() | ||
import Logger from './logger' | ||
|
||
/** | ||
* @param file 上传文件 | ||
|
@@ -22,7 +20,6 @@ function upload( | |
putExtra?: Partial<Extra>, | ||
config?: Partial<Config> | ||
): Observable<UploadProgress, CustomError, UploadCompleteData> { | ||
|
||
const options: UploadOptions = { | ||
file, | ||
key, | ||
|
@@ -31,12 +28,14 @@ function upload( | |
config | ||
} | ||
|
||
// 为每个任务创建单独的 Logger | ||
const logger = new Logger(token, config?.disableStatisticsReport, config?.debugLogLevel) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 要不要为每个任务的日志带上唯一的标识(类似 tracing 系统的 req id),以便在同时存在多个上传任务时区分不同任务的输出 |
||
return new Observable((observer: IObserver<UploadProgress, CustomError, UploadCompleteData>) => { | ||
const manager = createUploadManager(options, { | ||
onData: (data: UploadProgress) => observer.next(data), | ||
onError: (err: CustomError) => observer.error(err), | ||
onComplete: (res: any) => observer.complete(res) | ||
}, statisticsLogger) | ||
}, logger) | ||
manager.putFile() | ||
return manager.stop.bind(manager) | ||
}) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import Logger from './index' | ||
|
||
let isCallReport = false | ||
|
||
jest.mock('./report-v3', () => ({ | ||
reportV3: () => { | ||
isCallReport = true | ||
} | ||
})) | ||
|
||
const originalLog = console.log | ||
const originalWarn = console.warn | ||
const originalError = console.error | ||
|
||
const logMessage: unknown[] = [] | ||
const warnMessage: unknown[] = [] | ||
const errorMessage: unknown[] = [] | ||
|
||
beforeAll(() => { | ||
console.log = jest.fn((...args: unknown[]) => logMessage.push(...args)) | ||
console.warn = jest.fn((...args: unknown[]) => warnMessage.push(...args)) | ||
console.error = jest.fn((...args: unknown[]) => errorMessage.push(...args)) | ||
}) | ||
|
||
afterAll(() => { | ||
console.log = originalLog | ||
console.warn = originalWarn | ||
console.error = originalError | ||
}) | ||
|
||
describe('test logger', () => { | ||
test('test level', () => { | ||
const infoLogger = new Logger('', true, 'INFO') | ||
infoLogger.info('test1') | ||
expect(logMessage).toStrictEqual([`Qiniu-JS-SDK [INFO][1]: `, 'test1']) | ||
infoLogger.warn('test2') | ||
expect(warnMessage).toStrictEqual(['Qiniu-JS-SDK [WARN][1]: ', 'test2']) | ||
infoLogger.error('test3') | ||
expect(errorMessage).toStrictEqual(['Qiniu-JS-SDK [ERROR][1]: ', 'test3']) | ||
|
||
// 清空消息 | ||
logMessage.splice(0, logMessage.length) | ||
warnMessage.splice(0, warnMessage.length) | ||
errorMessage.splice(0, errorMessage.length) | ||
|
||
const warnLogger = new Logger('', true, 'WARN') | ||
warnLogger.info('test1') | ||
expect(logMessage).toStrictEqual([]) | ||
warnLogger.warn('test2') | ||
expect(warnMessage).toStrictEqual(['Qiniu-JS-SDK [WARN][2]: ', 'test2']) | ||
warnLogger.error('test3') | ||
expect(errorMessage).toStrictEqual(['Qiniu-JS-SDK [ERROR][2]: ', 'test3']) | ||
|
||
// 清空消息 | ||
logMessage.splice(0, logMessage.length) | ||
warnMessage.splice(0, warnMessage.length) | ||
errorMessage.splice(0, errorMessage.length) | ||
|
||
const errorLogger = new Logger('', true, 'ERROR') | ||
errorLogger.info('test1') | ||
expect(logMessage).toStrictEqual([]) | ||
errorLogger.warn('test2') | ||
expect(warnMessage).toStrictEqual([]) | ||
errorLogger.error('test3') | ||
expect(errorMessage).toStrictEqual(['Qiniu-JS-SDK [ERROR][3]: ', 'test3']) | ||
|
||
// 清空消息 | ||
logMessage.splice(0, logMessage.length) | ||
warnMessage.splice(0, warnMessage.length) | ||
errorMessage.splice(0, errorMessage.length) | ||
|
||
const offLogger = new Logger('', true, 'OFF') | ||
offLogger.info('test1') | ||
expect(logMessage).toStrictEqual([]) | ||
offLogger.warn('test2') | ||
expect(warnMessage).toStrictEqual([]) | ||
offLogger.error('test3') | ||
expect(errorMessage).toStrictEqual([]) | ||
}) | ||
|
||
test('test unique id', () => { | ||
// @ts-ignore | ||
const startId = Logger.id | ||
new Logger('', true, 'OFF') | ||
new Logger('', true, 'OFF') | ||
const last = new Logger('', true, 'OFF') | ||
// @ts-ignore | ||
expect(last.id).toStrictEqual(startId + 3) | ||
}) | ||
|
||
test('test report', () => { | ||
const logger1 = new Logger('', false, 'OFF') | ||
logger1.report(null as any) | ||
expect(isCallReport).toBeTruthy() | ||
isCallReport = false | ||
const logger2 = new Logger('', true, 'OFF') | ||
logger2.report(null as any) | ||
expect(isCallReport).toBeFalsy() | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { reportV3, V3LogInfo } from './report-v3' | ||
|
||
export type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'OFF' | ||
|
||
export default class Logger { | ||
private static id: number = 0 | ||
|
||
// 为每个类分配一个 id | ||
// 用以区分不同的上传任务 | ||
private id = ++Logger.id | ||
|
||
constructor( | ||
private token: string, | ||
private disableReport = true, | ||
private level: LogLevel = 'OFF' | ||
) { } | ||
|
||
/** | ||
* @param {V3LogInfo} data 上报的数据。 | ||
* @param {boolean} retry 重试次数,可选,默认为 3。 | ||
* @description 向服务端上报统计信息。 | ||
*/ | ||
report(data: V3LogInfo, retry?: number) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIP: 这么看的话把 |
||
if (this.disableReport) return | ||
try { reportV3(this.token, data, retry) } | ||
catch (error) { console.warn(error) } | ||
} | ||
|
||
/** | ||
* @param {unknown[]} ...args | ||
* @description 输出 info 级别的调试信息。 | ||
*/ | ||
info(...args: unknown[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const allowLevel: LogLevel[] = ['INFO'] | ||
if (allowLevel.includes(this.level)) { | ||
console.log(`Qiniu-JS-SDK [INFO][${this.id}]: `, ...args) | ||
} | ||
} | ||
|
||
/** | ||
* @param {unknown[]} ...args | ||
* @description 输出 warn 级别的调试信息。 | ||
*/ | ||
warn(...args: unknown[]) { | ||
const allowLevel: LogLevel[] = ['INFO', 'WARN'] | ||
if (allowLevel.includes(this.level)) { | ||
console.warn(`Qiniu-JS-SDK [WARN][${this.id}]: `, ...args) | ||
} | ||
} | ||
|
||
/** | ||
* @param {unknown[]} ...args | ||
* @description 输出 error 级别的调试信息。 | ||
*/ | ||
error(...args: unknown[]) { | ||
const allowLevel: LogLevel[] = ['INFO', 'WARN', 'ERROR'] | ||
if (allowLevel.includes(this.level)) { | ||
console.error(`Qiniu-JS-SDK [ERROR][${this.id}]: `, ...args) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { reportV3, V3LogInfo } from './report-v3' | ||
|
||
class MockXHR { | ||
sendData: string | ||
openData: string[] | ||
openCount: number | ||
headerData: string[] | ||
|
||
status: number | ||
readyState: number | ||
onreadystatechange() { } | ||
|
||
clear() { | ||
this.sendData = '' | ||
this.openData = [] | ||
this.headerData = [] | ||
|
||
this.status = 0 | ||
this.readyState = 0 | ||
} | ||
|
||
open(...args: string[]) { | ||
this.clear() | ||
this.openCount += 1 | ||
this.openData = args | ||
} | ||
|
||
send(args: string) { | ||
this.sendData = args | ||
} | ||
|
||
setRequestHeader(...args: string[]) { | ||
this.headerData.push(...args) | ||
} | ||
|
||
changeStatusAndState(readyState: number, status: number) { | ||
this.status = status | ||
this.readyState = readyState | ||
this.onreadystatechange() | ||
} | ||
} | ||
|
||
const mockXHR = new MockXHR() | ||
|
||
jest.mock('../utils', () => ({ | ||
createXHR: () => mockXHR, | ||
getAuthHeaders: (t: string) => t | ||
})) | ||
|
||
describe('test report-v3', () => { | ||
const testData: V3LogInfo = { | ||
code: 200, | ||
reqId: 'reqId', | ||
host: 'host', | ||
remoteIp: 'remoteIp', | ||
port: 'port', | ||
duration: 1, | ||
time: 1, | ||
bytesSent: 1, | ||
upType: 'jssdk-h5', | ||
size: 1 | ||
} | ||
|
||
test('test stringify send Data', () => { | ||
reportV3('token', testData, 3) | ||
mockXHR.changeStatusAndState(0, 0) | ||
expect(mockXHR.sendData).toBe([ | ||
testData.code || '', | ||
testData.reqId || '', | ||
testData.host || '', | ||
testData.remoteIp || '', | ||
testData.port || '', | ||
testData.duration || '', | ||
testData.time || '', | ||
testData.bytesSent || '', | ||
testData.upType || '', | ||
testData.size || '' | ||
].join(',')) | ||
}) | ||
|
||
test('test retry', () => { | ||
mockXHR.openCount = 0 | ||
reportV3('token', testData) | ||
for (let index = 1; index <= 10; index++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 10 次是随便定义的,大于传的 retry 次数就行 |
||
mockXHR.changeStatusAndState(4, 0) | ||
} | ||
expect(mockXHR.openCount).toBe(4) | ||
|
||
mockXHR.openCount = 0 | ||
reportV3('token', testData, 4) | ||
for (let index = 1; index < 10; index++) { | ||
mockXHR.changeStatusAndState(4, 0) | ||
} | ||
expect(mockXHR.openCount).toBe(5) | ||
|
||
mockXHR.openCount = 0 | ||
reportV3('token', testData, 0) | ||
for (let index = 1; index < 10; index++) { | ||
mockXHR.changeStatusAndState(4, 0) | ||
} | ||
expect(mockXHR.openCount).toBe(1) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { createXHR, getAuthHeaders } from '../utils' | ||
|
||
export interface V3LogInfo { | ||
code: number | ||
reqId: string | ||
host: string | ||
remoteIp: string | ||
port: string | ||
duration: number | ||
time: number | ||
bytesSent: number | ||
upType: 'jssdk-h5' | ||
size: number | ||
} | ||
|
||
/** | ||
* @param {string} token 上传使用的 token | ||
* @param {V3LogInfo} data 上报的统计数据 | ||
* @param {number} retry 重试的次数,默认值 3 | ||
* @description v3 版本的日志上传接口,参考文档 https://github.com/qbox/product/blob/master/kodo/uplog.md#%E7%89%88%E6%9C%AC-3。 | ||
*/ | ||
export function reportV3(token: string, data: V3LogInfo, retry = 3) { | ||
const xhr = createXHR() | ||
xhr.open('POST', 'https://uplog.qbox.me/log/3') | ||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') | ||
xhr.setRequestHeader('Authorization', getAuthHeaders(token).Authorization) | ||
xhr.onreadystatechange = () => { | ||
if (xhr.readyState === 4 && xhr.status !== 200 && retry > 0) { | ||
reportV3(token, data, retry - 1) | ||
} | ||
} | ||
|
||
// 顺序参考:https://github.com/qbox/product/blob/master/kodo/uplog.md#%E7%89%88%E6%9C%AC-3 | ||
const stringifyData = [ | ||
data.code || '', | ||
data.reqId || '', | ||
data.host || '', | ||
data.remoteIp || '', | ||
data.port || '', | ||
data.duration || '', | ||
data.time || '', | ||
data.bytesSent || '', | ||
data.upType || '', | ||
data.size || '' | ||
].join(',') | ||
|
||
xhr.send(stringifyData) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIP: 也许将来可以考虑结合 process env
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我也考虑到了,后面可能会这么做