Skip to content

Commit 850f8bb

Browse files
authored
[KODO-12017] 添加生产模式输出详细日志功能及配置 (#499)
1 parent 4a1da8c commit 850f8bb

File tree

16 files changed

+458
-111
lines changed

16 files changed

+458
-111
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
.vscode
23
node_modules
34
bower_components
45
demo/config.js

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ qiniu.compressImage(file, options).then(data => {
201201
* config.checkByMD5: 是否开启 MD5 校验,为布尔值;在断点续传时,开启 MD5 校验会将已上传的分片与当前分片进行 MD5 值比对,若不一致,则重传该分片,避免使用错误的分片。读取分片内容并计算 MD5 需要花费一定的时间,因此会稍微增加断点续传时的耗时,默认为 false,不开启。
202202
* config.forceDirect: 是否上传全部采用直传方式,为布尔值;为 `true` 时则上传方式全部为直传 form 方式,禁用断点续传,默认 `false`
203203
* config.chunkSize: `number`,分片上传时每片的大小,必须为正整数,单位为 `MB`,且最大不能超过 1024,默认值 4。因为 chunk 数最大 10000,所以如果文件以你所设的 `chunkSize` 进行分片并且 chunk 数超过 10000,我们会把你所设的 `chunkSize` 扩大二倍,如果仍不符合则继续扩大,直到符合条件。
204+
* config.debugLogLevel: `INFO` | `WARN` | `ERROR` | `OFF`,允许程序在控制台输出日志,默认为 `OFF`,不输出任何日志,本功能仅仅用于本地调试,不建议在线上环境开启。
204205

205206
* **putExtra**: `object`,其中的每一项都为可选
206207

src/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import StatisticsLogger from './statisticsLog'
21
import createUploadManager, { Extra, Config, UploadOptions, UploadProgress } from './upload'
32
import { Observable, IObserver } from './observable'
43
import { CustomError } from './utils'
54
import { UploadCompleteData } from './api'
65
import compressImage from './compress'
7-
8-
const statisticsLogger = new StatisticsLogger()
6+
import Logger from './logger'
97

108
/**
119
* @param file 上传文件
@@ -22,7 +20,6 @@ function upload(
2220
putExtra?: Partial<Extra>,
2321
config?: Partial<Config>
2422
): Observable<UploadProgress, CustomError, UploadCompleteData> {
25-
2623
const options: UploadOptions = {
2724
file,
2825
key,
@@ -31,12 +28,14 @@ function upload(
3128
config
3229
}
3330

31+
// 为每个任务创建单独的 Logger
32+
const logger = new Logger(token, config?.disableStatisticsReport, config?.debugLogLevel)
3433
return new Observable((observer: IObserver<UploadProgress, CustomError, UploadCompleteData>) => {
3534
const manager = createUploadManager(options, {
3635
onData: (data: UploadProgress) => observer.next(data),
3736
onError: (err: CustomError) => observer.error(err),
3837
onComplete: (res: any) => observer.complete(res)
39-
}, statisticsLogger)
38+
}, logger)
4039
manager.putFile()
4140
return manager.stop.bind(manager)
4241
})

src/logger/index.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import Logger from './index'
2+
3+
let isCallReport = false
4+
5+
jest.mock('./report-v3', () => ({
6+
reportV3: () => {
7+
isCallReport = true
8+
}
9+
}))
10+
11+
const originalLog = console.log
12+
const originalWarn = console.warn
13+
const originalError = console.error
14+
15+
const logMessage: unknown[] = []
16+
const warnMessage: unknown[] = []
17+
const errorMessage: unknown[] = []
18+
19+
beforeAll(() => {
20+
console.log = jest.fn((...args: unknown[]) => logMessage.push(...args))
21+
console.warn = jest.fn((...args: unknown[]) => warnMessage.push(...args))
22+
console.error = jest.fn((...args: unknown[]) => errorMessage.push(...args))
23+
})
24+
25+
afterAll(() => {
26+
console.log = originalLog
27+
console.warn = originalWarn
28+
console.error = originalError
29+
})
30+
31+
describe('test logger', () => {
32+
test('test level', () => {
33+
const infoLogger = new Logger('', true, 'INFO')
34+
infoLogger.info('test1')
35+
expect(logMessage).toStrictEqual([`Qiniu-JS-SDK [INFO][1]: `, 'test1'])
36+
infoLogger.warn('test2')
37+
expect(warnMessage).toStrictEqual(['Qiniu-JS-SDK [WARN][1]: ', 'test2'])
38+
infoLogger.error('test3')
39+
expect(errorMessage).toStrictEqual(['Qiniu-JS-SDK [ERROR][1]: ', 'test3'])
40+
41+
// 清空消息
42+
logMessage.splice(0, logMessage.length)
43+
warnMessage.splice(0, warnMessage.length)
44+
errorMessage.splice(0, errorMessage.length)
45+
46+
const warnLogger = new Logger('', true, 'WARN')
47+
warnLogger.info('test1')
48+
expect(logMessage).toStrictEqual([])
49+
warnLogger.warn('test2')
50+
expect(warnMessage).toStrictEqual(['Qiniu-JS-SDK [WARN][2]: ', 'test2'])
51+
warnLogger.error('test3')
52+
expect(errorMessage).toStrictEqual(['Qiniu-JS-SDK [ERROR][2]: ', 'test3'])
53+
54+
// 清空消息
55+
logMessage.splice(0, logMessage.length)
56+
warnMessage.splice(0, warnMessage.length)
57+
errorMessage.splice(0, errorMessage.length)
58+
59+
const errorLogger = new Logger('', true, 'ERROR')
60+
errorLogger.info('test1')
61+
expect(logMessage).toStrictEqual([])
62+
errorLogger.warn('test2')
63+
expect(warnMessage).toStrictEqual([])
64+
errorLogger.error('test3')
65+
expect(errorMessage).toStrictEqual(['Qiniu-JS-SDK [ERROR][3]: ', 'test3'])
66+
67+
// 清空消息
68+
logMessage.splice(0, logMessage.length)
69+
warnMessage.splice(0, warnMessage.length)
70+
errorMessage.splice(0, errorMessage.length)
71+
72+
const offLogger = new Logger('', true, 'OFF')
73+
offLogger.info('test1')
74+
expect(logMessage).toStrictEqual([])
75+
offLogger.warn('test2')
76+
expect(warnMessage).toStrictEqual([])
77+
offLogger.error('test3')
78+
expect(errorMessage).toStrictEqual([])
79+
})
80+
81+
test('test unique id', () => {
82+
// @ts-ignore
83+
const startId = Logger.id
84+
new Logger('', true, 'OFF')
85+
new Logger('', true, 'OFF')
86+
const last = new Logger('', true, 'OFF')
87+
// @ts-ignore
88+
expect(last.id).toStrictEqual(startId + 3)
89+
})
90+
91+
test('test report', () => {
92+
const logger1 = new Logger('', false, 'OFF')
93+
logger1.report(null as any)
94+
expect(isCallReport).toBeTruthy()
95+
isCallReport = false
96+
const logger2 = new Logger('', true, 'OFF')
97+
logger2.report(null as any)
98+
expect(isCallReport).toBeFalsy()
99+
})
100+
})

src/logger/index.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { reportV3, V3LogInfo } from './report-v3'
2+
3+
export type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'OFF'
4+
5+
export default class Logger {
6+
private static id: number = 0
7+
8+
// 为每个类分配一个 id
9+
// 用以区分不同的上传任务
10+
private id = ++Logger.id
11+
12+
constructor(
13+
private token: string,
14+
private disableReport = true,
15+
private level: LogLevel = 'OFF'
16+
) { }
17+
18+
/**
19+
* @param {V3LogInfo} data 上报的数据。
20+
* @param {boolean} retry 重试次数,可选,默认为 3。
21+
* @description 向服务端上报统计信息。
22+
*/
23+
report(data: V3LogInfo, retry?: number) {
24+
if (this.disableReport) return
25+
try { reportV3(this.token, data, retry) }
26+
catch (error) { console.warn(error) }
27+
}
28+
29+
/**
30+
* @param {unknown[]} ...args
31+
* @description 输出 info 级别的调试信息。
32+
*/
33+
info(...args: unknown[]) {
34+
const allowLevel: LogLevel[] = ['INFO']
35+
if (allowLevel.includes(this.level)) {
36+
console.log(`Qiniu-JS-SDK [INFO][${this.id}]: `, ...args)
37+
}
38+
}
39+
40+
/**
41+
* @param {unknown[]} ...args
42+
* @description 输出 warn 级别的调试信息。
43+
*/
44+
warn(...args: unknown[]) {
45+
const allowLevel: LogLevel[] = ['INFO', 'WARN']
46+
if (allowLevel.includes(this.level)) {
47+
console.warn(`Qiniu-JS-SDK [WARN][${this.id}]: `, ...args)
48+
}
49+
}
50+
51+
/**
52+
* @param {unknown[]} ...args
53+
* @description 输出 error 级别的调试信息。
54+
*/
55+
error(...args: unknown[]) {
56+
const allowLevel: LogLevel[] = ['INFO', 'WARN', 'ERROR']
57+
if (allowLevel.includes(this.level)) {
58+
console.error(`Qiniu-JS-SDK [ERROR][${this.id}]: `, ...args)
59+
}
60+
}
61+
}

src/logger/report-v3.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { reportV3, V3LogInfo } from './report-v3'
2+
3+
class MockXHR {
4+
sendData: string
5+
openData: string[]
6+
openCount: number
7+
headerData: string[]
8+
9+
status: number
10+
readyState: number
11+
onreadystatechange() { }
12+
13+
clear() {
14+
this.sendData = ''
15+
this.openData = []
16+
this.headerData = []
17+
18+
this.status = 0
19+
this.readyState = 0
20+
}
21+
22+
open(...args: string[]) {
23+
this.clear()
24+
this.openCount += 1
25+
this.openData = args
26+
}
27+
28+
send(args: string) {
29+
this.sendData = args
30+
}
31+
32+
setRequestHeader(...args: string[]) {
33+
this.headerData.push(...args)
34+
}
35+
36+
changeStatusAndState(readyState: number, status: number) {
37+
this.status = status
38+
this.readyState = readyState
39+
this.onreadystatechange()
40+
}
41+
}
42+
43+
const mockXHR = new MockXHR()
44+
45+
jest.mock('../utils', () => ({
46+
createXHR: () => mockXHR,
47+
getAuthHeaders: (t: string) => t
48+
}))
49+
50+
describe('test report-v3', () => {
51+
const testData: V3LogInfo = {
52+
code: 200,
53+
reqId: 'reqId',
54+
host: 'host',
55+
remoteIp: 'remoteIp',
56+
port: 'port',
57+
duration: 1,
58+
time: 1,
59+
bytesSent: 1,
60+
upType: 'jssdk-h5',
61+
size: 1
62+
}
63+
64+
test('test stringify send Data', () => {
65+
reportV3('token', testData, 3)
66+
mockXHR.changeStatusAndState(0, 0)
67+
expect(mockXHR.sendData).toBe([
68+
testData.code || '',
69+
testData.reqId || '',
70+
testData.host || '',
71+
testData.remoteIp || '',
72+
testData.port || '',
73+
testData.duration || '',
74+
testData.time || '',
75+
testData.bytesSent || '',
76+
testData.upType || '',
77+
testData.size || ''
78+
].join(','))
79+
})
80+
81+
test('test retry', () => {
82+
mockXHR.openCount = 0
83+
reportV3('token', testData)
84+
for (let index = 1; index <= 10; index++) {
85+
mockXHR.changeStatusAndState(4, 0)
86+
}
87+
expect(mockXHR.openCount).toBe(4)
88+
89+
mockXHR.openCount = 0
90+
reportV3('token', testData, 4)
91+
for (let index = 1; index < 10; index++) {
92+
mockXHR.changeStatusAndState(4, 0)
93+
}
94+
expect(mockXHR.openCount).toBe(5)
95+
96+
mockXHR.openCount = 0
97+
reportV3('token', testData, 0)
98+
for (let index = 1; index < 10; index++) {
99+
mockXHR.changeStatusAndState(4, 0)
100+
}
101+
expect(mockXHR.openCount).toBe(1)
102+
})
103+
})

src/logger/report-v3.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { createXHR, getAuthHeaders } from '../utils'
2+
3+
export interface V3LogInfo {
4+
code: number
5+
reqId: string
6+
host: string
7+
remoteIp: string
8+
port: string
9+
duration: number
10+
time: number
11+
bytesSent: number
12+
upType: 'jssdk-h5'
13+
size: number
14+
}
15+
16+
/**
17+
* @param {string} token 上传使用的 token
18+
* @param {V3LogInfo} data 上报的统计数据
19+
* @param {number} retry 重试的次数,默认值 3
20+
* @description v3 版本的日志上传接口,参考文档 https://github.com/qbox/product/blob/master/kodo/uplog.md#%E7%89%88%E6%9C%AC-3。
21+
*/
22+
export function reportV3(token: string, data: V3LogInfo, retry = 3) {
23+
const xhr = createXHR()
24+
xhr.open('POST', 'https://uplog.qbox.me/log/3')
25+
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
26+
xhr.setRequestHeader('Authorization', getAuthHeaders(token).Authorization)
27+
xhr.onreadystatechange = () => {
28+
if (xhr.readyState === 4 && xhr.status !== 200 && retry > 0) {
29+
reportV3(token, data, retry - 1)
30+
}
31+
}
32+
33+
// 顺序参考:https://github.com/qbox/product/blob/master/kodo/uplog.md#%E7%89%88%E6%9C%AC-3
34+
const stringifyData = [
35+
data.code || '',
36+
data.reqId || '',
37+
data.host || '',
38+
data.remoteIp || '',
39+
data.port || '',
40+
data.duration || '',
41+
data.time || '',
42+
data.bytesSent || '',
43+
data.upType || '',
44+
data.size || ''
45+
].join(',')
46+
47+
xhr.send(stringifyData)
48+
}

0 commit comments

Comments
 (0)