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 2 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 |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { createXHR } from './utils' | ||
|
||
export type LogLevel = '>=info' | '>=warn' | '>=error' | false | ||
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. NIP: 在我用过的 logger 的经验中,基本都约定 leve 设置为 另外如果是希望关掉,有 LogLevel |
||
|
||
export class Logger { | ||
lzfee0227 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
constructor( | ||
private token: string, | ||
private isReport = false, | ||
private level: LogLevel = false, | ||
) { } | ||
|
||
private stack(): string | undefined { | ||
const stack = new Error().stack?.split('\n') | ||
// 裁掉前三行,显示真正的函数调用点 | ||
return stack?.slice(3)[0]?.trim() | ||
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. 如当面提到的,可能需要多验证一些浏览器;或者 stack 干脆不处理 |
||
} | ||
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. logger 自身不会报错吧。。? |
||
|
||
private report(logs: any[], retry = 3) { | ||
if (!this.isReport) return | ||
|
||
const xhr = createXHR() | ||
xhr.open('POST', 'https://uplog.qbox.me/log/3') | ||
yinxulai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') | ||
xhr.setRequestHeader('Authorization', 'UpToken ' + this.token) | ||
xhr.onreadystatechange = () => { | ||
if (xhr.readyState === 4 && xhr.status !== 200 && retry > 0) { | ||
this.report(logs, retry - 1) | ||
} | ||
} | ||
|
||
const formattedLogs = logs.map(log => { | ||
try { return JSON.stringify(log) } | ||
catch (error) { return JSON.stringify(error) } | ||
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. 如果这就是你的需求,那 但实际上,这应该不是你的需求? 你要不要干脆限制一下 logs 的类型 = =… 不要搞 |
||
}) | ||
|
||
xhr.send(formattedLogs.join('\n')) | ||
} | ||
|
||
/** | ||
* @param {any[]} ...args | ||
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: TS 不用写 |
||
* 输出 info 级别的调试信息 | ||
*/ | ||
info(...args: any[]) { | ||
yinxulai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const allowLevel: LogLevel[] = ['>=info'] | ||
if (allowLevel.includes(this.level)) { | ||
console.log('Qiniu-JS-SDK[info]: ', ...args) | ||
yinxulai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/** | ||
* @param {any[]} ...args | ||
* 输出 warn 级别的调试信息 | ||
*/ | ||
warn(...args: any[]) { | ||
const allowLevel: LogLevel[] = ['>=info', '>=warn'] | ||
if (allowLevel.includes(this.level)) { | ||
console.warn('Qiniu-JS-SDK[warn]: ', ...args) | ||
} | ||
} | ||
|
||
/** | ||
* @param {any[]} ...args | ||
* 输出 error 级别的调试信息 | ||
*/ | ||
error(...args: any[]) { | ||
const allowLevel: LogLevel[] = ['>=info', '>=warn', '>=error'] | ||
if (allowLevel.includes(this.level)) { | ||
console.error('Qiniu-JS-SDK[error]: ', ...args) | ||
this.report([this.stack(), ...args]) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import * as utils from '../utils' | ||
import { getUploadUrl, UploadCompleteData } from '../api' | ||
|
||
import StatisticsLogger from '../statisticsLog' | ||
import { Logger, LogLevel } from '../logger' | ||
import { region } from '../config' | ||
|
||
export const DEFAULT_CHUNK_SIZE = 4 // 单位 MB | ||
|
@@ -32,14 +32,16 @@ export interface Config { | |
uphost: string | ||
/** 自定义分片上传并发请求量 */ | ||
concurrentRequestLimit: number | ||
/** 是否禁止静态日志上报 */ | ||
disableStatisticsReport: boolean | ||
/** 分片大小,单位为 MB */ | ||
chunkSize: number | ||
/** 上传域名协议 */ | ||
upprotocol: 'http:' | 'https:' | ||
/** 上传区域 */ | ||
region?: typeof region[keyof typeof region] | ||
/** 是否禁止静态日志上报 */ | ||
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. 修正为统计日志上报
yinxulai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
disableStatisticsReport: boolean | ||
/** 设置调试日志输出模式,默认 false,不输出任何日志 */ | ||
yinxulai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
debugLogLevel?: LogLevel | ||
yinxulai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
export interface UploadOptions { | ||
|
@@ -103,7 +105,7 @@ export default abstract class Base { | |
|
||
protected abstract run(): utils.Response<any> | ||
|
||
constructor(options: UploadOptions, handlers: UploadHandler, private statisticsLogger: StatisticsLogger) { | ||
constructor(options: UploadOptions, handlers: UploadHandler, protected logger: Logger) { | ||
this.config = { | ||
useCdnDomain: true, | ||
disableStatisticsReport: false, | ||
|
@@ -117,69 +119,80 @@ export default abstract class Base { | |
...options.config | ||
} | ||
|
||
logger.info('inited Config', this.config) | ||
|
||
this.putExtra = { | ||
fname: '', | ||
...options.putExtra | ||
} | ||
|
||
logger.info('inited putExtra', this.putExtra) | ||
|
||
this.file = options.file | ||
this.key = options.key | ||
this.token = options.token | ||
|
||
this.onData = handlers.onData | ||
this.onError = handlers.onError | ||
this.onComplete = handlers.onComplete | ||
|
||
try { | ||
this.bucket = utils.getPutPolicy(this.token).bucket | ||
} catch (e) { | ||
this.onError(e) | ||
logger.error('get bucket from token failed.', e) | ||
} | ||
} | ||
|
||
public async putFile(): Promise<utils.ResponseSuccess<UploadCompleteData>> { | ||
public async putFile(): Promise<void> { | ||
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. 要不注释一下吧 |
||
this.aborted = false | ||
if (!this.putExtra.fname) { | ||
this.logger.info('use file.name as fname.') | ||
this.putExtra.fname = this.file.name | ||
} | ||
|
||
if (this.file.size > 10000 * GB) { | ||
const err = new Error('file size exceed maximum value 10000G') | ||
const errorMessage = 'file size exceed maximum value 10000G' | ||
lzfee0227 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const err = new Error(errorMessage) | ||
this.logger.error(errorMessage) | ||
this.onError(err) | ||
throw err | ||
} | ||
|
||
if (this.putExtra.customVars) { | ||
if (!utils.isCustomVarsValid(this.putExtra.customVars)) { | ||
const err = new Error('customVars key should start width x:') | ||
const errorMessage = 'customVars key should start width x:' | ||
const err = new Error(errorMessage) | ||
this.logger.error(errorMessage) | ||
this.onError(err) | ||
throw err | ||
} | ||
} | ||
|
||
if (this.putExtra.metadata) { | ||
if (!utils.isMetaDataValid(this.putExtra.metadata)) { | ||
const err = new Error('metadata key should start with x-qn-meta-') | ||
const errorMessage = 'metadata key should start with x-qn-meta-' | ||
const err = new Error(errorMessage) | ||
this.logger.error(errorMessage) | ||
this.onError(err) | ||
throw err | ||
} | ||
} | ||
|
||
try { | ||
this.logger.info('getUploadUrl') | ||
this.uploadUrl = await getUploadUrl(this.config, this.token) | ||
this.uploadAt = new Date().getTime() | ||
|
||
const result = await this.run() | ||
this.onComplete(result.data) | ||
|
||
if (!this.config.disableStatisticsReport) { | ||
this.sendLog(result.reqId, 200) | ||
} | ||
|
||
return result | ||
|
||
this.sendLog(result.reqId, 200) // 收集成功的日志感觉没啥用? | ||
lzfee0227 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
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. 现在看来 按照原来的实现 我怎么觉得其实是这样的 this.putFIle().catch(err => {
// 程序日志,会中断流程、抛到全局 & 打印到控制台,至于是否上报是另一回事?
logger.error(err)
}) 然后这里的 |
||
} catch (err) { | ||
this.logger.error(err) | ||
|
||
this.clear() | ||
if (err.isRequestError && !this.config.disableStatisticsReport) { | ||
if (err.isRequestError) { | ||
const reqId = this.aborted ? '' : err.reqId | ||
const code = this.aborted ? -2 : err.code | ||
this.sendLog(reqId, code) | ||
|
@@ -191,20 +204,23 @@ export default abstract class Base { | |
// 1. 满足 needRetry 的条件且 retryCount 不为 0 | ||
// 2. uploadId 无效时在 resume 里会清除本地数据,并且这里触发重新上传 | ||
if (needRetry && notReachRetryCount || err.code === 612) { | ||
this.logger.warn(`error auto retry: ${this.retryCount}/${this.config.retryCount}`) | ||
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. 这还真不好说是 warn 还是 info 其实 |
||
return this.putFile() | ||
} | ||
|
||
this.onError(err) | ||
throw err | ||
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. MARK:所有的 error 都交给 onError,不应该再 throw 出去 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. 1、是否 throw 到 global 看一下 rxjs 的设定,比如是否跟绑定了 onError 有关 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. 要 at 恒哥来瞅一眼么.. 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.
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. 这里我当时可能是想表达 rejected status,因为 putFile 本身是返回一个 promise, 既然出错了,那这个 promise 的状态如果是 resolved, 感觉就不太合适。
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.
跟你说的不太一样啊 @yinxulai |
||
} | ||
} | ||
|
||
private clear() { | ||
this.logger.info('start cleaning all xhr.') | ||
this.xhrList.forEach(xhr => xhr.abort()) | ||
this.logger.info('cleanup completed.') | ||
this.xhrList = [] | ||
} | ||
|
||
public stop() { | ||
this.logger.info('stop.') | ||
this.clear() | ||
this.aborted = true | ||
} | ||
|
@@ -214,7 +230,7 @@ export default abstract class Base { | |
} | ||
|
||
private sendLog(reqId: string, code: number) { | ||
this.statisticsLogger.log({ | ||
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. 我看 logger 只有 所以以前这里的内容会记到服务端,现在不记到服务端了? |
||
this.logger.info({ | ||
code, | ||
reqId, | ||
host: utils.getDomainFromUrl(this.uploadUrl), | ||
|
@@ -225,7 +241,7 @@ export default abstract class Base { | |
bytesSent: this.progress ? this.progress.total.loaded : 0, | ||
upType: 'jssdk-h5', | ||
size: this.file.size | ||
}, this.token) | ||
}) | ||
} | ||
|
||
public getProgressInfoItem(loaded: number, size: number) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import Resume from './resume' | ||
import Direct from './direct' | ||
import { UploadOptions, UploadHandler } from './base' | ||
import StatisticsLogger from '../statisticsLog' | ||
import { Logger } from '../logger' | ||
import { MB } from '../utils' | ||
|
||
export * from './base' | ||
|
@@ -10,13 +10,18 @@ export * from './resume' | |
export default function createUploadManager( | ||
options: UploadOptions, | ||
handlers: UploadHandler, | ||
statisticsLogger: StatisticsLogger | ||
logger: Logger | ||
) { | ||
if (options.config && options.config.forceDirect) { | ||
return new Direct(options, handlers, statisticsLogger) | ||
logger.info('ues forceDirect mode.') | ||
return new Direct(options, handlers, logger) | ||
} | ||
|
||
return options.file.size > 4 * MB | ||
? new Resume(options, handlers, statisticsLogger) | ||
: new Direct(options, handlers, statisticsLogger) | ||
if (options.file.size > 4 * MB) { | ||
logger.info('file size over 4M, use Resume.') | ||
return new Resume(options, handlers, logger) | ||
} | ||
|
||
logger.info('file size less than 4M, use Direct.') | ||
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: less or equal |
||
return new Direct(options, handlers, logger) | ||
} |
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.
Logger
constructor 的第二个参数名是isReport
,这边传的是disable statistics report
,是不是反了?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.
要不要为每个任务的日志带上唯一的标识(类似 tracing 系统的 req id),以便在同时存在多个上传任务时区分不同任务的输出