diff options
author | Chocobozzz <me@florianbigard.com> | 2022-07-15 15:30:14 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-07-18 11:37:18 +0200 |
commit | 42b40636991b97fe818007fab19091764fc5db73 (patch) | |
tree | db431787c06ce898d22e91ff771f795219274fc6 /client/src/root-helpers | |
parent | 654d4ede7fa4d0faa71e49bcfab6b65a686397b2 (diff) | |
download | PeerTube-42b40636991b97fe818007fab19091764fc5db73.tar.gz PeerTube-42b40636991b97fe818007fab19091764fc5db73.tar.zst PeerTube-42b40636991b97fe818007fab19091764fc5db73.zip |
Add ability for client to create server logs
Diffstat (limited to 'client/src/root-helpers')
-rw-r--r-- | client/src/root-helpers/images.ts | 4 | ||||
-rw-r--r-- | client/src/root-helpers/index.ts | 1 | ||||
-rw-r--r-- | client/src/root-helpers/logger.ts | 138 | ||||
-rw-r--r-- | client/src/root-helpers/plugins-manager.ts | 23 |
4 files changed, 154 insertions, 12 deletions
diff --git a/client/src/root-helpers/images.ts b/client/src/root-helpers/images.ts index fb229ce6d..c4b09ec3c 100644 --- a/client/src/root-helpers/images.ts +++ b/client/src/root-helpers/images.ts | |||
@@ -1,8 +1,10 @@ | |||
1 | import { logger } from './logger' | ||
2 | |||
1 | function imageToDataURL (input: File | Blob) { | 3 | function imageToDataURL (input: File | Blob) { |
2 | return new Promise<string>(res => { | 4 | return new Promise<string>(res => { |
3 | const reader = new FileReader() | 5 | const reader = new FileReader() |
4 | 6 | ||
5 | reader.onerror = err => console.error('Cannot read input file.', err) | 7 | reader.onerror = err => logger.error('Cannot read input file.', err) |
6 | reader.onloadend = () => res(reader.result as string) | 8 | reader.onloadend = () => res(reader.result as string) |
7 | reader.readAsDataURL(input) | 9 | reader.readAsDataURL(input) |
8 | }) | 10 | }) |
diff --git a/client/src/root-helpers/index.ts b/client/src/root-helpers/index.ts index a19855761..86301eafa 100644 --- a/client/src/root-helpers/index.ts +++ b/client/src/root-helpers/index.ts | |||
@@ -2,6 +2,7 @@ export * from './users' | |||
2 | export * from './bytes' | 2 | export * from './bytes' |
3 | export * from './images' | 3 | export * from './images' |
4 | export * from './local-storage-utils' | 4 | export * from './local-storage-utils' |
5 | export * from './logger' | ||
5 | export * from './peertube-web-storage' | 6 | export * from './peertube-web-storage' |
6 | export * from './plugins-manager' | 7 | export * from './plugins-manager' |
7 | export * from './string' | 8 | export * from './string' |
diff --git a/client/src/root-helpers/logger.ts b/client/src/root-helpers/logger.ts new file mode 100644 index 000000000..cd559cfa7 --- /dev/null +++ b/client/src/root-helpers/logger.ts | |||
@@ -0,0 +1,138 @@ | |||
1 | import { ClientLogCreate } from '@shared/models/server' | ||
2 | import { peertubeLocalStorage } from './peertube-web-storage' | ||
3 | import { UserTokens } from './users' | ||
4 | |||
5 | export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void | ||
6 | export type LoggerLevel = 'info' | 'warn' | 'error' | ||
7 | |||
8 | export type LoggerMessage = string | Error | object | ||
9 | export type LoggerMeta = Error | { [ id: string ]: any, err?: Error } | ||
10 | |||
11 | declare global { | ||
12 | interface Window { | ||
13 | logger: Logger | ||
14 | } | ||
15 | } | ||
16 | |||
17 | class Logger { | ||
18 | private readonly hooks: { level: LoggerLevel, hook: LoggerHook }[] = [] | ||
19 | |||
20 | info (message: LoggerMessage, meta?: LoggerMeta) { | ||
21 | this.runHooks('info', message, meta) | ||
22 | |||
23 | if (meta) console.log(message, meta) | ||
24 | else console.log(message) | ||
25 | } | ||
26 | |||
27 | warn (message: LoggerMessage, meta?: LoggerMeta) { | ||
28 | this.runHooks('warn', message, meta) | ||
29 | |||
30 | if (meta) console.warn(message, meta) | ||
31 | else console.warn(message) | ||
32 | } | ||
33 | |||
34 | error (message: LoggerMessage, meta?: LoggerMeta) { | ||
35 | this.runHooks('error', message, meta) | ||
36 | |||
37 | if (meta) console.error(message, meta) | ||
38 | else console.error(message) | ||
39 | } | ||
40 | |||
41 | addHook (level: LoggerLevel, hook: LoggerHook) { | ||
42 | this.hooks.push({ level, hook }) | ||
43 | } | ||
44 | |||
45 | registerServerSending (serverUrl: string) { | ||
46 | this.addHook('warn', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('warn', message, meta))) | ||
47 | this.addHook('error', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('error', message, meta))) | ||
48 | } | ||
49 | |||
50 | sendClientLog (serverUrl: string, payload: ClientLogCreate | null) { | ||
51 | if (!payload) return | ||
52 | |||
53 | const headers = new Headers({ | ||
54 | Accept: 'application/json', | ||
55 | 'Content-Type': 'application/json' | ||
56 | }) | ||
57 | |||
58 | try { | ||
59 | const tokens = UserTokens.getUserTokens(peertubeLocalStorage) | ||
60 | |||
61 | if (tokens) headers.set('Authorization', `${tokens.tokenType} ${tokens.accessToken}`) | ||
62 | } catch (err) { | ||
63 | console.error('Cannot set tokens to client log sender.', { err }) | ||
64 | } | ||
65 | |||
66 | try { | ||
67 | fetch(serverUrl + '/api/v1/server/logs/client', { | ||
68 | headers, | ||
69 | method: 'POST', | ||
70 | body: JSON.stringify(payload) | ||
71 | }) | ||
72 | } catch (err) { | ||
73 | console.error('Cannot send client warn/error to server.', err) | ||
74 | } | ||
75 | } | ||
76 | |||
77 | private buildServerLogPayload (level: Extract<LoggerLevel, 'warn' | 'error'>, message: LoggerMessage, meta?: LoggerMeta) { | ||
78 | if (!message) return null | ||
79 | |||
80 | return { | ||
81 | message: this.buildMessageServerLogPayload(message), | ||
82 | userAgent: navigator.userAgent, | ||
83 | url: window.location.href, | ||
84 | level, | ||
85 | stackTrace: this.buildStackServerLogPayload(message, meta), | ||
86 | meta: this.buildMetaServerLogPayload(meta) | ||
87 | } | ||
88 | } | ||
89 | |||
90 | private buildMessageServerLogPayload (message: LoggerMessage) { | ||
91 | if (typeof message === 'string') return message | ||
92 | if (message instanceof Error) return message.message | ||
93 | |||
94 | return JSON.stringify(message) | ||
95 | } | ||
96 | |||
97 | private buildStackServerLogPayload (message: LoggerMessage, meta?: LoggerMeta) { | ||
98 | if (message instanceof Error) return message.stack | ||
99 | if (meta instanceof Error) return meta.stack | ||
100 | if (meta?.err instanceof Error) return meta.err.stack | ||
101 | |||
102 | return undefined | ||
103 | } | ||
104 | |||
105 | private buildMetaServerLogPayload (meta?: LoggerMeta) { | ||
106 | if (!meta) return undefined | ||
107 | if (meta instanceof Error) return undefined | ||
108 | |||
109 | let result: string | ||
110 | |||
111 | try { | ||
112 | result = JSON.stringify(meta, (key, value) => { | ||
113 | if (key === 'err') return undefined | ||
114 | |||
115 | return value | ||
116 | }) | ||
117 | } catch (err) { | ||
118 | console.error('Cannot stringify meta.', err) | ||
119 | } | ||
120 | |||
121 | return result | ||
122 | } | ||
123 | |||
124 | private runHooks (level: LoggerLevel, message: LoggerMessage, meta?: LoggerMeta) { | ||
125 | for (const hookObj of this.hooks) { | ||
126 | if (hookObj.level !== level) continue | ||
127 | |||
128 | hookObj.hook(message, meta) | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | const logger = window.logger || new Logger() | ||
134 | window.logger = logger | ||
135 | |||
136 | export { | ||
137 | logger | ||
138 | } | ||
diff --git a/client/src/root-helpers/plugins-manager.ts b/client/src/root-helpers/plugins-manager.ts index 49a19781b..37a52be72 100644 --- a/client/src/root-helpers/plugins-manager.ts +++ b/client/src/root-helpers/plugins-manager.ts | |||
@@ -21,6 +21,7 @@ import { | |||
21 | } from '@shared/models' | 21 | } from '@shared/models' |
22 | import { environment } from '../environments/environment' | 22 | import { environment } from '../environments/environment' |
23 | import { ClientScript } from '../types' | 23 | import { ClientScript } from '../types' |
24 | import { logger } from './logger' | ||
24 | 25 | ||
25 | interface HookStructValue extends RegisterClientHookOptions { | 26 | interface HookStructValue extends RegisterClientHookOptions { |
26 | plugin: ServerConfigPlugin | 27 | plugin: ServerConfigPlugin |
@@ -48,7 +49,7 @@ type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSetting | |||
48 | 49 | ||
49 | type OnClientRoute = (options: RegisterClientRouteOptions) => void | 50 | type OnClientRoute = (options: RegisterClientRouteOptions) => void |
50 | 51 | ||
51 | const logger = debug('peertube:plugins') | 52 | const debugLogger = debug('peertube:plugins') |
52 | 53 | ||
53 | class PluginsManager { | 54 | class PluginsManager { |
54 | private hooks: Hooks = {} | 55 | private hooks: Hooks = {} |
@@ -109,10 +110,10 @@ class PluginsManager { | |||
109 | const hookType = getHookType(hookName) | 110 | const hookType = getHookType(hookName) |
110 | 111 | ||
111 | for (const hook of this.hooks[hookName]) { | 112 | for (const hook of this.hooks[hookName]) { |
112 | console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name) | 113 | logger.info(`Running hook ${hookName} of plugin ${hook.plugin.name}`) |
113 | 114 | ||
114 | result = await internalRunHook(hook.handler, hookType, result, params, err => { | 115 | result = await internalRunHook(hook.handler, hookType, result, params, err => { |
115 | console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err) | 116 | logger.error(`Cannot run hook ${hookName} of script ${hook.clientScript.script} of plugin ${hook.plugin.name}`, err) |
116 | }) | 117 | }) |
117 | } | 118 | } |
118 | 119 | ||
@@ -170,7 +171,7 @@ class PluginsManager { | |||
170 | 171 | ||
171 | this.loadingScopes[scope] = true | 172 | this.loadingScopes[scope] = true |
172 | 173 | ||
173 | logger('Loading scope %s', scope) | 174 | debugLogger('Loading scope %s', scope) |
174 | 175 | ||
175 | try { | 176 | try { |
176 | if (!isReload) this.loadedScopes.push(scope) | 177 | if (!isReload) this.loadedScopes.push(scope) |
@@ -180,7 +181,7 @@ class PluginsManager { | |||
180 | this.loadingScopes[scope] = false | 181 | this.loadingScopes[scope] = false |
181 | this.pluginsLoaded[scope].next(true) | 182 | this.pluginsLoaded[scope].next(true) |
182 | 183 | ||
183 | logger('Nothing to load for scope %s', scope) | 184 | debugLogger('Nothing to load for scope %s', scope) |
184 | return | 185 | return |
185 | } | 186 | } |
186 | 187 | ||
@@ -200,9 +201,9 @@ class PluginsManager { | |||
200 | this.pluginsLoaded[scope].next(true) | 201 | this.pluginsLoaded[scope].next(true) |
201 | this.loadingScopes[scope] = false | 202 | this.loadingScopes[scope] = false |
202 | 203 | ||
203 | logger('Scope %s loaded', scope) | 204 | debugLogger('Scope %s loaded', scope) |
204 | } catch (err) { | 205 | } catch (err) { |
205 | console.error('Cannot load plugins by scope %s.', scope, err) | 206 | logger.error(`Cannot load plugins by scope ${scope}`, err) |
206 | } | 207 | } |
207 | } | 208 | } |
208 | 209 | ||
@@ -211,7 +212,7 @@ class PluginsManager { | |||
211 | 212 | ||
212 | const registerHook = (options: RegisterClientHookOptions) => { | 213 | const registerHook = (options: RegisterClientHookOptions) => { |
213 | if (clientHookObject[options.target] !== true) { | 214 | if (clientHookObject[options.target] !== true) { |
214 | console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name) | 215 | logger.error(`Unknown hook ${options.target} of plugin ${plugin.name}. Skipping.`) |
215 | return | 216 | return |
216 | } | 217 | } |
217 | 218 | ||
@@ -252,7 +253,7 @@ class PluginsManager { | |||
252 | 253 | ||
253 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) | 254 | const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) |
254 | 255 | ||
255 | console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) | 256 | logger.info(`Loading script ${clientScript.script} of plugin ${plugin.name}`) |
256 | 257 | ||
257 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script | 258 | const absURL = (environment.apiUrl || window.location.origin) + clientScript.script |
258 | return dynamicImport(absURL) | 259 | return dynamicImport(absURL) |
@@ -266,7 +267,7 @@ class PluginsManager { | |||
266 | }) | 267 | }) |
267 | }) | 268 | }) |
268 | .then(() => this.sortHooksByPriority()) | 269 | .then(() => this.sortHooksByPriority()) |
269 | .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) | 270 | .catch(err => logger.error(`Cannot import or register plugin ${pluginInfo.plugin.name}`, err)) |
270 | } | 271 | } |
271 | 272 | ||
272 | private sortHooksByPriority () { | 273 | private sortHooksByPriority () { |
@@ -294,7 +295,7 @@ async function dynamicImport (url: string) { | |||
294 | // eslint-disable-next-line no-new-func | 295 | // eslint-disable-next-line no-new-func |
295 | return new Function(`return import('${url}')`)() | 296 | return new Function(`return import('${url}')`)() |
296 | } catch { | 297 | } catch { |
297 | console.log('Fallback to import polyfill') | 298 | logger.info('Fallback to import polyfill') |
298 | 299 | ||
299 | return new Promise((resolve, reject) => { | 300 | return new Promise((resolve, reject) => { |
300 | const vector = '$importModule$' + Math.random().toString(32).slice(2) | 301 | const vector = '$importModule$' + Math.random().toString(32).slice(2) |