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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +130 -24
  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
+ }