-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MM-59996] Make stats and client logs more easily accessible through slash commands #840
Changes from 1 commit
da07424
61348bf
f40649b
7710471
915b0f5
54750ae
ced5539
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,14 @@ import {EventEmitter} from 'events'; | |
import {deflate} from 'pako/lib/deflate'; | ||
import {AudioDevices, CallsClientConfig, CallsClientStats, TrackInfo} from 'src/types/types'; | ||
|
||
import {logDebug, logErr, logInfo, logWarn} from './log'; | ||
import {logDebug, logErr, logInfo, logWarn, persistClientLogs} from './log'; | ||
import {getScreenStream} from './utils'; | ||
import {WebSocketClient, WebSocketError, WebSocketErrorType} from './websocket'; | ||
import { | ||
STORAGE_CALLS_CLIENT_STATS_KEY, | ||
STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY, | ||
STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY, | ||
} from 'src/constants'; | ||
|
||
export const AudioInputPermissionsError = new Error('missing audio input permissions'); | ||
export const AudioInputMissingError = new Error('no audio input available'); | ||
|
@@ -99,8 +104,8 @@ export default class CallsClient extends EventEmitter { | |
}; | ||
} | ||
|
||
const defaultInputID = window.localStorage.getItem('calls_default_audio_input'); | ||
const defaultOutputID = window.localStorage.getItem('calls_default_audio_output'); | ||
const defaultInputID = window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
const defaultOutputID = window.localStorage.getItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY); | ||
if (defaultInputID && !this.currentAudioInputDevice) { | ||
const devices = this.audioDevices.inputs.filter((dev) => { | ||
return dev.deviceId === defaultInputID; | ||
|
@@ -114,7 +119,7 @@ export default class CallsClient extends EventEmitter { | |
this.currentAudioInputDevice = devices[0]; | ||
} else { | ||
logDebug('audio input device not found'); | ||
window.localStorage.removeItem('calls_default_audio_input'); | ||
window.localStorage.removeItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY); | ||
} | ||
} | ||
|
||
|
@@ -128,7 +133,7 @@ export default class CallsClient extends EventEmitter { | |
this.currentAudioOutputDevice = devices[0]; | ||
} else { | ||
logDebug('audio output device not found'); | ||
window.localStorage.removeItem('calls_default_audio_output'); | ||
window.localStorage.removeItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY); | ||
} | ||
} | ||
|
||
|
@@ -407,14 +412,15 @@ export default class CallsClient extends EventEmitter { | |
this.removeAllListeners('mos'); | ||
window.removeEventListener('beforeunload', this.onBeforeUnload); | ||
navigator.mediaDevices?.removeEventListener('devicechange', this.onDeviceChange); | ||
persistClientLogs(); | ||
} | ||
|
||
public async setAudioInputDevice(device: MediaDeviceInfo) { | ||
if (!this.peer) { | ||
return; | ||
} | ||
|
||
window.localStorage.setItem('calls_default_audio_input', device.deviceId); | ||
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_INPUT_KEY, device.deviceId); | ||
this.currentAudioInputDevice = device; | ||
|
||
// We emit this event so it's easier to keep state in sync between widget and pop out. | ||
|
@@ -464,7 +470,7 @@ export default class CallsClient extends EventEmitter { | |
if (!this.peer) { | ||
return; | ||
} | ||
window.localStorage.setItem('calls_default_audio_output', device.deviceId); | ||
window.localStorage.setItem(STORAGE_CALLS_DEFAULT_AUDIO_OUTPUT_KEY, device.deviceId); | ||
this.currentAudioOutputDevice = device; | ||
|
||
// We emit this event so it's easier to keep state in sync between widget and pop out. | ||
|
@@ -484,7 +490,8 @@ export default class CallsClient extends EventEmitter { | |
this.closed = true; | ||
if (this.peer) { | ||
this.getStats().then((stats) => { | ||
sessionStorage.setItem('calls_client_stats', JSON.stringify(stats)); | ||
const storage = window.desktop ? localStorage : sessionStorage; | ||
enzowritescode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
storage.setItem(STORAGE_CALLS_CLIENT_STATS_KEY, JSON.stringify(stats)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't want to overcomplicate it by adding listeners, so the stats are saved when the client disconnects (user leaves call) and can be retrieved after that. This slightly differs from Web browser behavior, where stats can be fetched at any time during or after a call, but the simple fix is enough to cover the customer's use case (troubleshooting). |
||
}).catch((statsErr) => { | ||
logErr(statsErr); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,54 @@ | ||
/* eslint-disable no-console */ | ||
|
||
import {STORAGE_CALLS_CLIENT_LOGS_KEY} from 'src/constants'; | ||
|
||
import {pluginId} from './manifest'; | ||
|
||
let clientLogs = ''; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ js |
||
|
||
function appendClientLog(level: string, ...args: unknown[]) { | ||
clientLogs += `${level} [${new Date().toISOString()}] ${args}\n`; | ||
} | ||
|
||
export function persistClientLogs() { | ||
const storage = window.desktop ? localStorage : sessionStorage; | ||
storage.setItem(STORAGE_CALLS_CLIENT_LOGS_KEY, clientLogs); | ||
clientLogs = ''; | ||
} | ||
|
||
export function getClientLogs() { | ||
const storage = window.desktop ? localStorage : sessionStorage; | ||
return storage.getItem(STORAGE_CALLS_CLIENT_LOGS_KEY) || ''; | ||
} | ||
|
||
export function logErr(...args: unknown[]) { | ||
console.error(`${pluginId}:`, ...args); | ||
try { | ||
if (window.callsClient) { | ||
appendClientLog('error', ...args); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
|
||
export function logWarn(...args: unknown[]) { | ||
console.warn(`${pluginId}:`, ...args); | ||
if (window.callsClient) { | ||
appendClientLog('warn', ...args); | ||
} | ||
} | ||
|
||
export function logInfo(...args: unknown[]) { | ||
console.info(`${pluginId}:`, ...args); | ||
if (window.callsClient) { | ||
appendClientLog('info', ...args); | ||
} | ||
} | ||
|
||
export function logDebug(...args: unknown[]) { | ||
// TODO: convert to debug once we are out of beta. | ||
console.info(`${pluginId}:`, ...args); | ||
console.debug(`${pluginId}:`, ...args); | ||
if (window.callsClient) { | ||
appendClientLog('debug', ...args); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if subCmd == subCommandCXyz { ... } format in this function is getting kind of long.
Either in this PR or another can we shorten it?
Maybe something like the following (or a switch):
if subCmd == subCommandXyz {
return subCommandXyzHelper()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, having a long function that reads simply top-to-bottom can be easier and quicker to read and understand, compared to having to jump back and forth to/from functions. So there's an argument for keeping it simple, too. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpoile single responsibility principle + easier to write tests for / understand the code coverage, especially if you use an IDE tool that shows highlights for code coverage.
I'm fine with leaving it as is if nobody else thinks it's an issue, but it's definitely a code smell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is I can make this change if you all want in another ticket.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@enzowritescode I added a wrapping utility to clean it up a little. Let me know if that works.