Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 126 additions & 47 deletions react/features/base/logging/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _appWillMount({ getState }: IStore, next: Function, action: AnyAction) {
const { config } = getState()['features/base/logging'];
function _appWillMount(store: IStore, next: Function, action: AnyAction) {
const { config } = store.getState()['features/base/logging'];

_setLogLevels(Logger, config);

_initEarlyLogging(store);

// FIXME Until the logic of conference.js is rewritten into the React
// app we, JitsiMeetJS.init is to not be used for the React app.
// Consequently, LIB_WILL_INIT will not be dispatched. In the meantime, do
Expand All @@ -85,6 +87,32 @@
return next(action);
}

/**
* Create the LogCollector and register it as the global log transport. It
* is done early to capture as much logs as possible. Captured logs will be
* cached, before the JitsiMeetLogStorage gets ready (RTCStats trace is
* available).
*
* @param {Store} store - The Redux store in which context the early logging is to be
* initialized.
* @private
* @returns {void}
*/
function _initEarlyLogging({ dispatch, getState }: IStore) {
const { logCollector } = getState()['features/base/logging'];

if (!logCollector) {
// Create the LogCollector with minimal configuration
// Use default values since config is not available yet
const _logCollector = new Logger.LogCollector(new JitsiMeetLogStorage(getState), {});

Logger.addGlobalTransport(_logCollector as any);
_logCollector.start();

dispatch(setLogCollector(_logCollector));
}
}

/**
* Starts the log collector, after {@link CONFERENCE_JOINED} action is reduced.
*
Expand Down Expand Up @@ -143,58 +171,109 @@
*/
function _initLogging({ dispatch, getState }: IStore,
loggingConfig: any, isTestingEnabled: boolean) {
const { logCollector } = getState()['features/base/logging'];
const state = getState();
const { analytics: { rtcstatsLogFlushSizeBytes } = {}, apiLogLevels } = state['features/base/config'];
const { logCollector } = state['features/base/logging'];
const { disableLogCollector } = loggingConfig;

// Create the LogCollector and register it as the global log transport. It
// is done early to capture as much logs as possible. Captured logs will be
// cached, before the JitsiMeetLogStorage gets ready (RTCStats trace is
// available).
if (!logCollector && !loggingConfig.disableLogCollector) {
const { apiLogLevels, analytics: { rtcstatsLogFlushSizeBytes } = {} } = getState()['features/base/config'];

// The smaller the flush size the smaller the chance of losing logs, but
// the more often the logs will be sent to the server, by default the LogCollector
// will set once the logs reach 10KB or 30 seconds have passed since the last flush,
// this means if something happens between that interval and the logs don't get flushed
// they will be lost, for instance the meeting tab is closed, the browser crashes,
// an uncaught exception happens, etc.
// If undefined is passed the default values will be used,
const _logCollector = new Logger.LogCollector(new JitsiMeetLogStorage(getState), {
maxEntryLength: rtcstatsLogFlushSizeBytes
});

if (apiLogLevels && Array.isArray(apiLogLevels) && typeof APP === 'object') {
const transport = buildExternalApiLogTransport(apiLogLevels);

Logger.addGlobalTransport(transport);
JitsiMeetJS.addGlobalLogTransport(transport);
}

Logger.addGlobalTransport(_logCollector);
if (disableLogCollector) {
_disableLogCollector(logCollector, dispatch);

_logCollector.start();
return;
}

dispatch(setLogCollector(_logCollector));
_setupExternalApiTransport(apiLogLevels);
_setupDebugLogging(isTestingEnabled);
_configureLogCollector(logCollector, rtcstatsLogFlushSizeBytes);
}

// The JitsiMeetInMemoryLogStorage can not be accessed on mobile through
// the 'executeScript' method like it's done in torture tests for WEB.
if (isTestingEnabled && typeof APP === 'object') {
APP.debugLogs = new JitsiMeetInMemoryLogStorage();
const debugLogCollector = new Logger.LogCollector(
APP.debugLogs, { storeInterval: 1000 });

Logger.addGlobalTransport(debugLogCollector);
JitsiMeetJS.addGlobalLogTransport(debugLogCollector);
debugLogCollector.start();
}
} else if (logCollector && loggingConfig.disableLogCollector) {
Logger.removeGlobalTransport(logCollector);
JitsiMeetJS.removeGlobalLogTransport(logCollector);
logCollector.stop();
dispatch(setLogCollector(undefined));
/**
* Sets up external API log transport if configured.
*
* @param {Array} apiLogLevels - The API log levels configuration.
* @private
* @returns {void}
*/
function _setupExternalApiTransport(apiLogLevels: any) {
if (apiLogLevels && Array.isArray(apiLogLevels) && typeof APP === 'object') {
const transport = buildExternalApiLogTransport(apiLogLevels);

Logger.addGlobalTransport(transport);
JitsiMeetJS.addGlobalLogTransport(transport);
}
}

/**
* Sets up debug logging for testing mode.
*
* @param {boolean} isTestingEnabled - Whether testing mode is enabled.
* @private
* @returns {void}
*/
function _setupDebugLogging(isTestingEnabled: boolean) {
// The JitsiMeetInMemoryLogStorage cannot be accessed on mobile through
// the 'executeScript' method like it's done in torture tests for WEB.
if (!isTestingEnabled || typeof APP !== 'object') {
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition logic has been inverted. The original code checked if (isTestingEnabled && typeof APP === 'object') but now it returns early when either condition is false, which means the debug logging setup will never execute.

Copilot uses AI. Check for mistakes.
return;
}

APP.debugLogs = new JitsiMeetInMemoryLogStorage();
const debugLogCollector = new Logger.LogCollector(
APP.debugLogs,

Check failure on line 222 in react/features/base/logging/middleware.ts

View workflow job for this annotation

GitHub Actions / Lint

Trailing spaces not allowed
{ storeInterval: 1000 }
);

Logger.addGlobalTransport(debugLogCollector);
JitsiMeetJS.addGlobalLogTransport(debugLogCollector);
debugLogCollector.start();
}

/**
* Configures the existing log collector with flush size settings.
*
* @param {Object} logCollector - The log collector instance.
* @param {number|undefined} rtcstatsLogFlushSizeBytes - The flush size in bytes.
* @private
* @returns {void}
*/
function _configureLogCollector(logCollector: any, rtcstatsLogFlushSizeBytes: number | undefined) {

// Log collector should be available at this point, as we initialize it early at appMount in
// order for it to capture and cache as many logs as possible with it's default behavior.
// We then update it's settings here after the config is in it's final form.
// Data will only be sent to the server once the RTCStats trace is available, if it's available.
if (!logCollector) {
return;
}

// The smaller the flush size, the smaller the chance of losing logs, but
// the more often the logs will be sent to the server. By default, the LogCollector
// will flush once the logs reach 10KB or 30 seconds have passed since the last flush.
// This means if something happens between that interval and the logs don't get flushed,
// they will be lost (e.g., meeting tab is closed, browser crashes, uncaught exception).
// If undefined is passed, the default values will be used.
logCollector.maxEntryLength = rtcstatsLogFlushSizeBytes;
Copy link

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly assigning to logCollector.maxEntryLength may not properly update the log collector's configuration. The original code passed this value during construction. Consider using a proper configuration method if available.

Suggested change
logCollector.maxEntryLength = rtcstatsLogFlushSizeBytes;
if (typeof logCollector.setMaxEntryLength === 'function') {
logCollector.setMaxEntryLength(rtcstatsLogFlushSizeBytes);
} else {
// Fallback to direct assignment if setter is not available.
logCollector.maxEntryLength = rtcstatsLogFlushSizeBytes;
}

Copilot uses AI. Check for mistakes.
}

/**
* Disables and cleans up the log collector.
*
* @param {Object} logCollector - The log collector instance.
* @param {Function} dispatch - The Redux dispatch function.
* @private
* @returns {void}
*/
function _disableLogCollector(logCollector: any, dispatch: Function) {
if (!logCollector) {
return;
}

Logger.removeGlobalTransport(logCollector);
JitsiMeetJS.removeGlobalLogTransport(logCollector);
logCollector.stop();
dispatch(setLogCollector(undefined));
}

/**
* Notifies the feature base/logging that the action {@link LIB_WILL_INIT} is
* being dispatched within a specific Redux {@code store}.
Expand Down
1 change: 1 addition & 0 deletions react/features/base/logging/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface ILoggingState {
config: ILoggingConfig;
logCollector?: {
flush: () => void;
maxEntryLength?: number;
start: () => void;
stop: () => void;
};
Expand Down
Loading