import { ClientLogCreate } from '@shared/models/server'
import { peertubeLocalStorage } from './peertube-web-storage'
import { OAuthUserTokens } 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)
this.clientWarn(message, meta)
}
clientWarn (message: LoggerMessage, meta?: LoggerMeta) {
if (meta) console.warn(message, meta)
else console.warn(message)
}
error (message: LoggerMessage, meta?: LoggerMeta) {
this.runHooks('error', message, meta)
this.clientError(message, meta)
}
clientError (message: LoggerMessage, meta?: LoggerMeta) {
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 = OAuthUserTokens.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
}