@wutiange/log-listener-plugin 1.3.1 → 2.0.1-alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. package/README.md +155 -2
  2. package/dist/src/HTTPInterceptor.d.ts +1 -0
  3. package/dist/src/HTTPInterceptor.js +12 -9
  4. package/dist/src/HTTPInterceptor.js.map +1 -1
  5. package/dist/src/Server.d.ts +13 -6
  6. package/dist/src/Server.js +119 -41
  7. package/dist/src/Server.js.map +1 -1
  8. package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.js +0 -1
  9. package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.js.map +1 -1
  10. package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js +0 -1
  11. package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js.map +1 -1
  12. package/dist/src/__tests__/Server.test.js +89 -114
  13. package/dist/src/__tests__/Server.test.js.map +1 -1
  14. package/dist/src/common.d.ts +19 -10
  15. package/dist/src/common.js +63 -4
  16. package/dist/src/common.js.map +1 -1
  17. package/dist/src/logPlugin.d.ts +12 -9
  18. package/dist/src/logPlugin.js +87 -82
  19. package/dist/src/logPlugin.js.map +1 -1
  20. package/dist/src/logger.d.ts +6 -0
  21. package/dist/src/logger.js +16 -0
  22. package/dist/src/logger.js.map +1 -0
  23. package/dist/src/utils.js +12 -7
  24. package/dist/src/utils.js.map +1 -1
  25. package/index.ts +3 -0
  26. package/package.json +18 -12
  27. package/src/HTTPInterceptor.ts +339 -0
  28. package/src/Server.ts +166 -0
  29. package/src/__mocks__/react-native/Libraries/Blob/FileReader.js +45 -0
  30. package/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js +39 -0
  31. package/src/__tests__/HTTPInterceptor.test.ts +322 -0
  32. package/src/__tests__/Server.test.ts +175 -0
  33. package/src/__tests__/utils.test.ts +113 -0
  34. package/src/common.ts +70 -0
  35. package/src/logPlugin.ts +224 -0
  36. package/src/logger.ts +15 -0
  37. package/src/utils.ts +112 -0
  38. package/tsconfig.json +27 -0
@@ -0,0 +1,224 @@
1
+ import Server from './Server';
2
+ import { createClassWithErrorHandling } from './utils';
3
+ import { httpInterceptor } from './HTTPInterceptor';
4
+ import { DEFAULT_TIMEOUT, getDefaultStorage, Level, LOG_KEY, Tag, URLS_KEY } from './common';
5
+ import logger from './logger';
6
+
7
+ type Options = {
8
+ /**
9
+ * storage 用于存储已设置的日志系统的 url
10
+ * @default @react-native-async-storage/async-storage
11
+ */
12
+ storage?: Storage
13
+ /**
14
+ * 设置上传日志的超时时间,单位为毫秒
15
+ * @default 3000
16
+ */
17
+ timeout?: number
18
+ /**
19
+ * 日志系统的url
20
+ */
21
+ testUrl?: string
22
+ /**
23
+ * 是否自动开启日志记录
24
+ * @default false
25
+ */
26
+ isAuto?: boolean
27
+ /**
28
+ * 设置日志系统的基础数据,这些数据会自动添加到每条日志中
29
+ */
30
+ baseData?: Record<string, any>
31
+ }
32
+
33
+ class LogPlugin {
34
+ private server: Server | null = null;
35
+ private timeout: number | null = null;
36
+ private isAuto = false
37
+ private storage: Storage | null = getDefaultStorage();
38
+
39
+ constructor() {
40
+ this.init()
41
+ }
42
+
43
+ private init = async () => {
44
+ this.server = new Server();
45
+ if (!this.storage) {
46
+ logger.warn(LOG_KEY, '你并没有设置 storage ,这会导致 App 杀死后可能需要重新加入日志系统才能收集日志数据,建议你设置 storage 。')
47
+ } else {
48
+ const urlsStr = await this.storage.getItem(URLS_KEY)
49
+ if (urlsStr) {
50
+ const urls = JSON.parse(urlsStr)
51
+ this.server.setBaseUrlObj(urls)
52
+ }
53
+ }
54
+
55
+
56
+ this.server.addUrlsListener((_, urlsObj) => {
57
+ if (this.storage) {
58
+ this.storage.setItem(URLS_KEY, JSON.stringify(urlsObj))
59
+ }
60
+ httpInterceptor.setIgnoredUrls(this.handleIgnoredUrls())
61
+ })
62
+ }
63
+
64
+ config = ({ storage, timeout, testUrl, isAuto, baseData = {} }: Options) => {
65
+ if (isAuto) {
66
+ this.auto()
67
+ } else {
68
+ this.unAuto()
69
+ }
70
+ this.storage = storage ?? getDefaultStorage();
71
+ this.setTimeout(timeout ?? DEFAULT_TIMEOUT)
72
+ this.setBaseUrl(testUrl)
73
+ this.setBaseData(baseData)
74
+ };
75
+
76
+ /**
77
+ * @deprecated 这个方法将在下一个主要版本中被移除。请使用 config({isAuto: true}) 替代。
78
+ */
79
+ auto = () => {
80
+ this.startRecordNetwork();
81
+ this.startRecordLog();
82
+ this.isAuto = true
83
+ }
84
+
85
+ unAuto = () => {
86
+ this.stopRecordLog()
87
+ httpInterceptor.disable()
88
+ httpInterceptor.removeAllListener()
89
+ this.isAuto = false
90
+ }
91
+
92
+ startRecordLog = () => {
93
+ console.log = (...data: any[]) => {
94
+ logger.log(...data)
95
+ this.log(...data);
96
+ };
97
+
98
+ console.warn = (...data: any[]) => {
99
+ logger.warn(...data)
100
+ this.warn(...data);
101
+ };
102
+
103
+ console.error = (...data: any[]) => {
104
+ logger.error(...data)
105
+ this.error(...data);
106
+ };
107
+ }
108
+
109
+ stopRecordLog = () => {
110
+ console.log = logger.log
111
+ console.warn = logger.warn
112
+ console.error = logger.error
113
+ }
114
+
115
+ private handleIgnoredUrls = () => {
116
+ const urls = this.server?.getUrls?.()
117
+ let ignoredUrls: string[] = []
118
+ if (urls?.length) {
119
+ ignoredUrls = urls.reduce((acc, url) => {
120
+ acc.push(`${url}/log`, `${url}/network`, `${url}/join`)
121
+ return acc
122
+ }, [] as string[])
123
+ }
124
+ return ignoredUrls
125
+ }
126
+
127
+ startRecordNetwork = () => {
128
+ httpInterceptor.addListener("send", (data) => {
129
+ this.server?.network({
130
+ url: data.url,
131
+ id: data.id,
132
+ method: data.method,
133
+ headers: data.requestHeaders,
134
+ body: data.requestData,
135
+ createTime: data.startTime
136
+ })
137
+ })
138
+ httpInterceptor.addListener("response", (data) => {
139
+ this.server?.network({
140
+ headers: data.responseHeaders,
141
+ body: data.responseData,
142
+ requestId: data.id,
143
+ statusCode: data.status,
144
+ endTime: data.endTime
145
+ })
146
+ })
147
+ httpInterceptor.enable({ignoredUrls: this.handleIgnoredUrls()})
148
+ }
149
+
150
+ /**
151
+ * @deprecated 这个方法将在下一个主要版本中被移除。请使用 config({testUrl: ''}) 替代。
152
+ */
153
+ setBaseUrl = (url: string) => {
154
+ const tempUrl = url?.trim()
155
+ if (this.server) {
156
+ this.server.updateUrl(tempUrl);
157
+ } else {
158
+ this.server = new Server(tempUrl);
159
+ }
160
+ httpInterceptor.setIgnoredUrls(this.handleIgnoredUrls())
161
+ if (this.isAuto) {
162
+ this.startRecordNetwork();
163
+ this.startRecordLog()
164
+ }
165
+ }
166
+
167
+ /**
168
+ * @deprecated 这个方法将在下一个主要版本中被移除。请使用 config({timeout: 3000}) 替代。
169
+ */
170
+ setTimeout = (timeout: number) => {
171
+ if (typeof timeout === 'number') {
172
+ this.timeout = timeout;
173
+ this.server?.updateTimeout(this.timeout);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * @deprecated 这个方法将在下一个主要版本中被移除。移除后将不再支持获取超时时间。
179
+ */
180
+ getTimeout = () => {
181
+ if (typeof this.timeout === 'number') {
182
+ return this.timeout;
183
+ }
184
+ return null;
185
+ }
186
+
187
+ /**
188
+ * @deprecated 这个方法将在下一个主要版本中被移除。请使用 config({baseData: {}}) 替代。
189
+ */
190
+ setBaseData = (data: Record<string, any> = {}) => {
191
+ this.server.updateBaseData(data)
192
+ }
193
+
194
+ private _log = (level: string, tag: string, ...data: any[]) => {
195
+ const sendData = {
196
+ message: data,
197
+ tag,
198
+ level: level ?? 'log',
199
+ createTime: Date.now(),
200
+ };
201
+ this.server?.log(sendData);
202
+ }
203
+
204
+ tag = (tag: string, ...data: any[]) => {
205
+ this._log(Level.LOG, tag, ...data);
206
+ }
207
+
208
+ log = (...data: any[]) => {
209
+ this._log(Level.LOG, Tag.DEFAULT, ...data);
210
+ }
211
+
212
+ warn = (...data: any[]) => {
213
+ this._log(Level.WARN, Tag.DEFAULT, ...data);
214
+ }
215
+
216
+ error = (...data: any[]) => {
217
+ this._log(Level.ERROR, Tag.DEFAULT, ...data);
218
+ }
219
+
220
+ }
221
+ const SafeLogPlugin = createClassWithErrorHandling(LogPlugin)
222
+ const logPlugin = new SafeLogPlugin();
223
+ export { SafeLogPlugin };
224
+ export default logPlugin;
package/src/logger.ts ADDED
@@ -0,0 +1,15 @@
1
+ const [log, warn, error] = [console.log, console.warn, console.error];
2
+
3
+ const logger = {
4
+ log: (...data: any[]) => {
5
+ log(...data)
6
+ },
7
+ warn: (...data: any[]) => {
8
+ warn(...data)
9
+ },
10
+ error: (...data: any[]) => {
11
+ error(...data)
12
+ },
13
+ }
14
+
15
+ export default logger
package/src/utils.ts ADDED
@@ -0,0 +1,112 @@
1
+ import URL from "url-parse";
2
+ import logger from "./logger";
3
+
4
+ export function sleep(ms: number, isReject: boolean = false) {
5
+ return new Promise((resolve, reject) => {
6
+ setTimeout(isReject ? () => reject({
7
+ code: 11001,
8
+ key: '@wutiange/log-listener-plugin%%timeout',
9
+ message: 'Timeout'
10
+ }) : resolve, ms)
11
+ })
12
+ }
13
+
14
+ // 检查 url 是否有端口号,不包含内置的端口号,比如 80 ,443 等
15
+ export function hasPort(url: string) {
16
+ // 如果 url 是空的或不是字符串,返回 false
17
+ if (!url || typeof url !== 'string') {
18
+ return false;
19
+ }
20
+
21
+ try {
22
+ // 使用 URL 构造函数解析 URL
23
+ const parsedUrl = new URL(url);
24
+
25
+ // 检查 port 属性是否为空
26
+ // 注意:如果使用默认端口(如 HTTP 的 80 或 HTTPS 的 443),port 会是空字符串
27
+ return parsedUrl.port !== '';
28
+ } catch (error) {
29
+ logger.error(error)
30
+ // 如果 URL 无效,捕获错误并返回 false
31
+ return false;
32
+ }
33
+ }
34
+
35
+
36
+ type Constructor<T = {}> = new (...args: any[]) => T;
37
+
38
+ export function createClassWithErrorHandling<T extends Constructor>(BaseClass: T): T {
39
+ return new Proxy(BaseClass, {
40
+ construct(target: T, args: any[]): object {
41
+ const instance = new target(...args);
42
+ return new Proxy(instance, {
43
+ get(target: any, prop: string | symbol): any {
44
+ const value = target[prop];
45
+ if (typeof value === 'function') {
46
+ return function(this: any, ...args: any[]): any {
47
+ try {
48
+ const result = value.apply(this, args);
49
+ if (result instanceof Promise) {
50
+ return result.catch((error: Error) => {
51
+ console.error(`Error in ${String(prop)}:`, error);
52
+ throw error; // 重新抛出错误,以便调用者可以捕获它
53
+ });
54
+ }
55
+ return result;
56
+ } catch (error) {
57
+ console.error(`Error in ${String(prop)}:`, error);
58
+ throw error; // 重新抛出错误,以便调用者可以捕获它
59
+ }
60
+ };
61
+ }
62
+ return value;
63
+ },
64
+ set(target: any, prop: string | symbol, value: any): boolean {
65
+ if (typeof value === 'function') {
66
+ target[prop] = function(this: any, ...args: any[]): any {
67
+ try {
68
+ const result = value.apply(this, args);
69
+ if (result instanceof Promise) {
70
+ return result.catch((error: Error) => {
71
+ console.error(`Error in ${String(prop)}:`, error);
72
+ throw error;
73
+ });
74
+ }
75
+ return result;
76
+ } catch (error) {
77
+ console.error(`Error in ${String(prop)}:`, error);
78
+ throw error;
79
+ }
80
+ };
81
+ } else {
82
+ target[prop] = value;
83
+ }
84
+ return true;
85
+ }
86
+ });
87
+ }
88
+ });
89
+ }
90
+
91
+
92
+ export function formDataToString(formData: FormData): string {
93
+ const boundary =
94
+ '----WebKitFormBoundary' + Math.random().toString(36).substr(2);
95
+ let result = '';
96
+ // 这是 react-native 中的实现,这里面是存在这个方法的
97
+ const parts = (formData as any).getParts();
98
+ for (const part of parts) {
99
+ result += `--${boundary}\r\n`;
100
+ result += `Content-Disposition: ${part.headers['content-disposition']}\r\n`;
101
+ if (part.headers['content-type']) {
102
+ result += `Content-Type: ${part.headers['content-type']}\r\n`;
103
+ }
104
+ const value = 'string' in part ? part.string : part.uri;
105
+ result += `Content-Length: ${value.length}\r\n\r\n`;
106
+ result += `${value}\r\n`;
107
+ }
108
+ result += `--${boundary}--\r\n`;
109
+ return result;
110
+ }
111
+
112
+
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "NodeNext",
4
+ "declaration": true,
5
+ "noImplicitAny": true,
6
+ "removeComments": true,
7
+ "preserveConstEnums": true,
8
+ "outDir": "dist",
9
+ "sourceMap": true,
10
+ "target": "es6",
11
+ "moduleResolution": "NodeNext",
12
+ "allowJs": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "allowSyntheticDefaultImports": true
18
+ },
19
+ "exclude": ["node_modules", "**/*.spec.ts"],
20
+ "include": [
21
+ "src/**/*",
22
+ "console.ts",
23
+ "fetch.ts",
24
+ "index.ts",
25
+ "react-native-extensions.d.ts"
26
+ ]
27
+ }