aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/root-helpers
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-07-15 15:30:14 +0200
committerChocobozzz <me@florianbigard.com>2022-07-18 11:37:18 +0200
commit42b40636991b97fe818007fab19091764fc5db73 (patch)
treedb431787c06ce898d22e91ff771f795219274fc6 /client/src/root-helpers
parent654d4ede7fa4d0faa71e49bcfab6b65a686397b2 (diff)
downloadPeerTube-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.ts4
-rw-r--r--client/src/root-helpers/index.ts1
-rw-r--r--client/src/root-helpers/logger.ts138
-rw-r--r--client/src/root-helpers/plugins-manager.ts23
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 @@
1import { logger } from './logger'
2
1function imageToDataURL (input: File | Blob) { 3function 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'
2export * from './bytes' 2export * from './bytes'
3export * from './images' 3export * from './images'
4export * from './local-storage-utils' 4export * from './local-storage-utils'
5export * from './logger'
5export * from './peertube-web-storage' 6export * from './peertube-web-storage'
6export * from './plugins-manager' 7export * from './plugins-manager'
7export * from './string' 8export * 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 @@
1import { ClientLogCreate } from '@shared/models/server'
2import { peertubeLocalStorage } from './peertube-web-storage'
3import { UserTokens } from './users'
4
5export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void
6export type LoggerLevel = 'info' | 'warn' | 'error'
7
8export type LoggerMessage = string | Error | object
9export type LoggerMeta = Error | { [ id: string ]: any, err?: Error }
10
11declare global {
12 interface Window {
13 logger: Logger
14 }
15}
16
17class 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
133const logger = window.logger || new Logger()
134window.logger = logger
135
136export {
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'
22import { environment } from '../environments/environment' 22import { environment } from '../environments/environment'
23import { ClientScript } from '../types' 23import { ClientScript } from '../types'
24import { logger } from './logger'
24 25
25interface HookStructValue extends RegisterClientHookOptions { 26interface HookStructValue extends RegisterClientHookOptions {
26 plugin: ServerConfigPlugin 27 plugin: ServerConfigPlugin
@@ -48,7 +49,7 @@ type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSetting
48 49
49type OnClientRoute = (options: RegisterClientRouteOptions) => void 50type OnClientRoute = (options: RegisterClientRouteOptions) => void
50 51
51const logger = debug('peertube:plugins') 52const debugLogger = debug('peertube:plugins')
52 53
53class PluginsManager { 54class 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)