1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
import { ClientLogCreate } from '@shared/models/server'
import { peertubeLocalStorage } from './peertube-web-storage'
import { UserTokens } from './users'
export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void
export type LoggerLevel = 'info' | 'warn' | 'error'
export type LoggerMessage = string | Error | object
export type LoggerMeta = Error | { [ id: string ]: any, err?: Error }
declare global {
interface Window {
logger: Logger
}
}
class Logger {
private readonly hooks: { level: LoggerLevel, hook: LoggerHook }[] = []
info (message: LoggerMessage, meta?: LoggerMeta) {
this.runHooks('info', message, meta)
if (meta) console.log(message, meta)
else console.log(message)
}
warn (message: LoggerMessage, meta?: LoggerMeta) {
this.runHooks('warn', message, meta)
if (meta) console.warn(message, meta)
else console.warn(message)
}
error (message: LoggerMessage, meta?: LoggerMeta) {
this.runHooks('error', message, meta)
if (meta) console.error(message, meta)
else console.error(message)
}
addHook (level: LoggerLevel, hook: LoggerHook) {
this.hooks.push({ level, hook })
}
registerServerSending (serverUrl: string) {
this.addHook('warn', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('warn', message, meta)))
this.addHook('error', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('error', message, meta)))
}
sendClientLog (serverUrl: string, payload: ClientLogCreate | null) {
if (!payload) return
const headers = new Headers({
Accept: 'application/json',
'Content-Type': 'application/json'
})
try {
const tokens = UserTokens.getUserTokens(peertubeLocalStorage)
if (tokens) headers.set('Authorization', `${tokens.tokenType} ${tokens.accessToken}`)
} catch (err) {
console.error('Cannot set tokens to client log sender.', { err })
}
try {
fetch(serverUrl + '/api/v1/server/logs/client', {
headers,
method: 'POST',
body: JSON.stringify(payload)
})
} catch (err) {
console.error('Cannot send client warn/error to server.', err)
}
}
private buildServerLogPayload (level: Extract<LoggerLevel, 'warn' | 'error'>, message: LoggerMessage, meta?: LoggerMeta) {
if (!message) return null
return {
message: this.buildMessageServerLogPayload(message),
userAgent: navigator.userAgent,
url: window.location.href,
level,
stackTrace: this.buildStackServerLogPayload(message, meta),
meta: this.buildMetaServerLogPayload(meta)
}
}
private buildMessageServerLogPayload (message: LoggerMessage) {
if (typeof message === 'string') return message
if (message instanceof Error) return message.message
return JSON.stringify(message)
}
private buildStackServerLogPayload (message: LoggerMessage, meta?: LoggerMeta) {
if (message instanceof Error) return this.buildStack(message)
if (meta instanceof Error) return this.buildStack(meta)
if (meta?.err instanceof Error) return this.buildStack(meta.err)
return undefined
}
private buildMetaServerLogPayload (meta?: LoggerMeta) {
if (!meta) return undefined
if (meta instanceof Error) return undefined
let result: string
try {
result = JSON.stringify(meta, (key, value) => {
if (key === 'err') return undefined
return value
})
} catch (err) {
console.error('Cannot stringify meta.', err)
}
return result
}
private runHooks (level: LoggerLevel, message: LoggerMessage, meta?: LoggerMeta) {
for (const hookObj of this.hooks) {
if (hookObj.level !== level) continue
hookObj.hook(message, meta)
}
}
private buildStack (err: Error) {
return `${err.message}\n${err.stack || ''}`
}
}
const logger = window.logger || new Logger()
window.logger = logger
export {
logger
}
|