aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/root-helpers/logger.ts
blob: d1fdf73aaa0ee3f4d48746b3bd3746f4dfabcb00 (plain) (blame)
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
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)

    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 = 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
}