@sdc-monitor/demo
Version:
<div align="center"> <a href="#" target="_blank"> <img src="https://i.loli.net/2021/07/28/EvPwd4NjVH3tBfO.jpg" alt="mito-logo" height="90"> </a> <p>A Lightweight SDK For Monitor Web</p>
190 lines (183 loc) • 6.65 kB
text/typescript
import {
WxBreadcrumbTypes,
ErrorTypes,
globalVar,
HttpTypes,
HTTP_CODE,
MethodTypes,
WxEventTypes,
BREADCRUMBCATEGORYS,
WxBaseEventTypes
} from '@sdc-monitor/shared'
import { fromHttpStatus, getCurrentRoute, getRealPath, getTimestamp, Severity, SpanStatus, variableTypeDetection } from '@sdc-monitor/utils'
import { BasePluginType, HttpCollectedType, HttpTransformedType } from '@sdc-monitor/types'
import { WxClient } from '../wxClient'
import { addBreadcrumbInWx } from '../utils'
enum WxXhrTypes {
request = 'request',
downloadFile = 'downloadFile',
uploadFile = 'uploadFile'
}
const wxRequestPlugin: BasePluginType<WxEventTypes, WxClient> = {
name: WxBaseEventTypes.REQUEST,
monitor(notify) {
monitorWxXhr.call(this, notify)
},
transform(collectedData: HttpCollectedType) {
return httpTransform(collectedData)
},
consumer(transformedData: HttpTransformedType) {
httpTransformedDataConsumer.call(this, transformedData)
}
}
function monitorWxXhr(this: WxClient, notify: (eventName: WxEventTypes, data: any) => void) {
const hookMethods = Object.keys(WxXhrTypes)
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this
const { options: wxOptions } = this
hookMethods.forEach((hook) => {
const originRequest = wx[hook]
Object.defineProperty(wx, hook, {
writable: true,
enumerable: true,
configurable: true,
value: function (...args: any[]) {
// 拿到入参
const options: WechatMiniprogram.RequestOption | WechatMiniprogram.DownloadFileOption | WechatMiniprogram.UploadFileOption = args[0]
const { url } = options
let method: string,
reqData = undefined
if ((options as WechatMiniprogram.RequestOption).method) {
method = (options as WechatMiniprogram.RequestOption).method
} else if (hook === WxXhrTypes.downloadFile) {
method = MethodTypes.Get
} else {
method = MethodTypes.Post
}
let header = options.header
!header && (header = {})
if ((method === MethodTypes.Post && that.transport.isSelfDsn(url)) || wxOptions.isFilterHttpUrl(url)) {
return originRequest.call(this, options)
}
switch (hook) {
case WxXhrTypes.request:
reqData = (options as WechatMiniprogram.RequestOption).data
break
case WxXhrTypes.downloadFile:
reqData = {
filePath: (options as WechatMiniprogram.DownloadFileOption).filePath
}
break
default:
// uploadFile
reqData = {
filePath: (options as WechatMiniprogram.UploadFileOption).filePath,
name: (options as WechatMiniprogram.UploadFileOption).name
}
}
// 收集小程序的请求信息
const httpCollect: HttpCollectedType = {
request: {
httpType: HttpTypes.XHR,
url,
method,
data: reqData
},
response: {},
time: getTimestamp()
}
wxOptions.setTraceId(url, (headerFieldName, traceId) => {
httpCollect.request.traceId = traceId
header[headerFieldName] = traceId
})
function setRequestHeader(key: string, value: string) {
header[key] = value
}
wxOptions.beforeAppAjaxSend && wxOptions.beforeAppAjaxSend({ method, url }, { setRequestHeader })
// 成功回调
const successHandler:
| WechatMiniprogram.RequestSuccessCallback
| WechatMiniprogram.DownloadFileSuccessCallback
| WechatMiniprogram.UploadFileFailCallback = function (res) {
const endTime = getTimestamp()
httpCollect.response.data = (variableTypeDetection.isString(res.data) || variableTypeDetection.isObject(res.data)) && res.data
httpCollect.elapsedTime = endTime - httpCollect.time
httpCollect.response.status = res.statusCode
httpCollect.errMsg = res.errMsg
notify(WxBaseEventTypes.REQUEST, httpCollect)
if (variableTypeDetection.isFunction(options.success)) {
return options.success(res)
}
}
const _fail = options.fail
// 失败回调
const failHandler:
| WechatMiniprogram.RequestFailCallback
| WechatMiniprogram.DownloadFileFailCallback
| WechatMiniprogram.UploadFileFailCallback = function (err) {
// 系统和网络层面的失败
const endTime = getTimestamp()
httpCollect.elapsedTime = endTime - httpCollect.time
httpCollect.errMsg = err.errMsg
httpCollect.response.status = 0
notify(WxBaseEventTypes.REQUEST, httpCollect)
if (variableTypeDetection.isFunction(_fail)) {
return _fail(err)
}
}
const actOptions = {
...options,
success: successHandler,
fail: failHandler
}
// return 原始函数
return originRequest.call(this, actOptions)
}
})
})
}
export function httpTransform(httpCollectedData: HttpCollectedType): HttpTransformedType {
let message = ''
const {
request: { httpType, method, url },
response: { status },
elapsedTime
} = httpCollectedData
const name = `${httpType}--${method}`
if (status === 0) {
message =
elapsedTime <= globalVar.crossOriginThreshold ? 'http请求失败,失败原因:跨域限制或域名不存在' : 'http请求失败,失败原因:超时'
} else {
message = fromHttpStatus(status)
}
message = message === SpanStatus.Ok ? message : `${message} ${getRealPath(url)}`
return {
...httpCollectedData,
type: ErrorTypes.HTTP,
url: getCurrentRoute(),
level: Severity.Low,
message,
name
}
}
export function httpTransformedDataConsumer(this: WxClient, transformedData: HttpTransformedType) {
const type = WxBreadcrumbTypes.XHR
// time 是为了保持顺序,紧跟在点击事件后面
const {
response: { status },
time
} = transformedData
const isError = status === 0 || status === HTTP_CODE.BAD_REQUEST || status > HTTP_CODE.UNAUTHORIZED
addBreadcrumbInWx.call(this, transformedData, type, Severity.Info, { time })
if (isError) {
const breadcrumbStack = this.breadcrumb.push({
type,
category: BREADCRUMBCATEGORYS.EXCEPTION,
data: { ...transformedData },
level: Severity.Error,
time
})
this.transport.send(transformedData, breadcrumbStack)
}
}
export default wxRequestPlugin