Merge pull request #4088 from element-hq/toger5/logger-getChild-fix

fix getChild before rageshake setup
This commit is contained in:
Timo
2026-07-08 18:19:04 +02:00
committed by GitHub
14 changed files with 132 additions and 31 deletions

View File

@@ -34,6 +34,7 @@
"/*\nCopyright %%CURRENT_YEAR%% Element Creations Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE in the repository root for full details.\n*/\n\n" "/*\nCopyright %%CURRENT_YEAR%% Element Creations Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE in the repository root for full details.\n*/\n\n"
], ],
"element-call/no-observablescope-leak": "error", "element-call/no-observablescope-leak": "error",
"element-call/no-top-level-logger-get-child": "error",
"jsdoc/empty-tags": "error", "jsdoc/empty-tags": "error",
"jsdoc/check-property-names": "error", "jsdoc/check-property-names": "error",
"jsdoc/require-param-description": "warn", "jsdoc/require-param-description": "warn",

View File

@@ -0,0 +1,92 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { ESLintUtils } from "@typescript-eslint/utils";
/**
* Node types that introduce a new non-module scope. A getChild() call nested
* inside any of these is considered "not at the top level".
*/
const FUNCTION_OR_CLASS_TYPES = new Set([
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"ClassBody",
]);
const rule = ESLintUtils.RuleCreator(
() => "https://github.com/element-hq/element-call",
)({
name: "no-top-level-logger-get-child",
meta: {
type: "problem",
docs: {
description:
"Disallow calling logger.getChild() at the top level of a module." +
"`getChild` has to be called after the rageshake logger `init()`." +
"If it is called at the top level the child logger will never be setup for rageshakes.",
},
messages: {
noTopLevelGetChild:
"Do not call logger.getChild() at the top level of a module; move it inside a function or class instead that gets called after rageshake logger `init()` is called.",
},
schema: [],
},
create(context) {
// Tracks the local binding names that refer to the logger imported from
// 'matrix-js-sdk/lib/logger', e.g. both `logger` and `rootLogger` in:
// import { logger } from "matrix-js-sdk/lib/logger";
// import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
const loggerNames = new Set();
return {
ImportDeclaration(node) {
if (node.source.value !== "matrix-js-sdk/lib/logger") return;
for (const specifier of node.specifiers) {
if (
specifier.type === "ImportSpecifier" &&
specifier.imported.name === "logger"
) {
loggerNames.add(specifier.local.name);
}
}
},
CallExpression(node) {
// Must be a non-computed member expression call: something.getChild(...)
if (
node.callee.type !== "MemberExpression" ||
node.callee.computed ||
node.callee.property.type !== "Identifier" ||
node.callee.property.name !== "getChild"
)
return;
// The receiver must be one of the tracked logger names.
const object = node.callee.object;
if (object.type !== "Identifier" || !loggerNames.has(object.name))
return;
// Flag the call only when it is at module top level — i.e. there is no
// enclosing function or class body anywhere in the ancestor chain.
const ancestors = context.sourceCode.getAncestors(node);
const isTopLevel = !ancestors.some((a) =>
FUNCTION_OR_CLASS_TYPES.has(a.type),
);
if (isTopLevel) {
context.report({
messageId: "noTopLevelGetChild",
node,
});
}
},
};
},
});
export default rule;

View File

@@ -2,5 +2,7 @@ module.exports = {
rules: { rules: {
"copyright-header": require("./CopyrightHeader").default, "copyright-header": require("./CopyrightHeader").default,
"no-observablescope-leak": require("./NoObservableScopeLeak").default, "no-observablescope-leak": require("./NoObservableScopeLeak").default,
"no-top-level-logger-get-child": require("./NoTopLevelLoggerGetChild")
.default,
}, },
}; };

View File

@@ -15,9 +15,8 @@ import { scan } from "rxjs";
import { type WidgetHelpers } from "../src/widget"; import { type WidgetHelpers } from "../src/widget";
import { type LivekitRoomItem } from "../src/state/CallViewModel/CallViewModel"; import { type LivekitRoomItem } from "../src/state/CallViewModel/CallViewModel";
export const logger = rootLogger.getChild("[MatrixRTCSdk]");
export const tryMakeSticky = (widget: WidgetHelpers): void => { export const tryMakeSticky = (widget: WidgetHelpers): void => {
const logger = rootLogger.getChild("[MatrixRTCSdk]");
logger.info("try making sticky MatrixRTCSdk"); logger.info("try making sticky MatrixRTCSdk");
void widget.api void widget.api
.setAlwaysOnScreen(true) .setAlwaysOnScreen(true)

View File

@@ -52,7 +52,8 @@ import { getUrlParams } from "../src/UrlParams";
import { MuteStates } from "../src/state/MuteStates"; import { MuteStates } from "../src/state/MuteStates";
import { MediaDevices } from "../src/state/MediaDevices"; import { MediaDevices } from "../src/state/MediaDevices";
import { E2eeType } from "../src/e2ee/e2eeType"; import { E2eeType } from "../src/e2ee/e2eeType";
import { currentAndPrev, logger, TEXT_LK_TOPIC, tryMakeSticky } from "./helper"; import { currentAndPrev, TEXT_LK_TOPIC, tryMakeSticky } from "./helper";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { import {
ElementWidgetActions, ElementWidgetActions,
widget as _widget, widget as _widget,
@@ -104,6 +105,7 @@ export async function createMatrixRTCSdk(
id: string = "", id: string = "",
sticky: boolean = false, sticky: boolean = false,
): Promise<MatrixRTCSdk> { ): Promise<MatrixRTCSdk> {
const logger = rootLogger.getChild("[MatrixRTCSdk]");
const scope = new ObservableScope(); const scope = new ObservableScope();
// widget client // widget client

View File

@@ -10,15 +10,15 @@ import {
type MatrixRTCSession, type MatrixRTCSession,
MatrixRTCSessionEvent, MatrixRTCSessionEvent,
} from "matrix-js-sdk/lib/matrixrtc"; } from "matrix-js-sdk/lib/matrixrtc";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
const logger = rootLogger.getChild("[MatrixKeyProvider]");
export class MatrixKeyProvider extends BaseKeyProvider { export class MatrixKeyProvider extends BaseKeyProvider {
private rtcSession?: MatrixRTCSession; private rtcSession?: MatrixRTCSession;
private logger: Logger;
public constructor() { public constructor() {
super({ ratchetWindowSize: 10, keyringSize: 256 }); super({ ratchetWindowSize: 10, keyringSize: 256 });
this.logger = rootLogger.getChild("[MatrixKeyProvider]");
} }
public setRTCSession(rtcSession: MatrixRTCSession): void { public setRTCSession(rtcSession: MatrixRTCSession): void {
@@ -60,12 +60,12 @@ export class MatrixKeyProvider extends BaseKeyProvider {
encryptionKeyIndex, encryptionKeyIndex,
); );
logger.debug( this.logger.debug(
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${rtcBackendIdentity} (before hash: ${membershipParts.userId}:${membershipParts.deviceId}) encryptionKeyIndex=${encryptionKeyIndex}`, `Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${rtcBackendIdentity} (before hash: ${membershipParts.userId}:${membershipParts.deviceId}) encryptionKeyIndex=${encryptionKeyIndex}`,
); );
}, },
(e) => { (e) => {
logger.error( this.logger.error(
`Failed to create key material from buffer for livekit room=${this.rtcSession?.room.roomId} participantId before hash=${membershipParts.userId}:${membershipParts.deviceId} encryptionKeyIndex=${encryptionKeyIndex}`, `Failed to create key material from buffer for livekit room=${this.rtcSession?.room.roomId} participantId before hash=${membershipParts.userId}:${membershipParts.deviceId} encryptionKeyIndex=${encryptionKeyIndex}`,
e, e,
); );

View File

@@ -14,7 +14,7 @@ import {
AudioTrack, AudioTrack,
type AudioTrackProps, type AudioTrackProps,
} from "@livekit/components-react"; } from "@livekit/components-react";
import { logger } from "matrix-js-sdk/lib/logger"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { useEarpieceAudioConfig } from "../MediaDevicesContext"; import { useEarpieceAudioConfig } from "../MediaDevicesContext";
import { useReactiveState } from "../useReactiveState"; import { useReactiveState } from "../useReactiveState";
@@ -40,7 +40,6 @@ export interface MatrixAudioRendererProps {
muted?: boolean; muted?: boolean;
} }
const prefixedLogger = logger.getChild("[MatrixAudioRenderer]");
/** /**
* Takes care of handling remote participants audio tracks and makes sure that microphones and screen share are audible. * Takes care of handling remote participants audio tracks and makes sure that microphones and screen share are audible.
* *
@@ -60,6 +59,7 @@ export function LivekitRoomAudioRenderer({
validIdentities, validIdentities,
muted, muted,
}: MatrixAudioRendererProps): ReactNode { }: MatrixAudioRendererProps): ReactNode {
const logger = rootLogger.getChild("[MatrixAudioRenderer]");
const tracks = useTracks( const tracks = useTracks(
[ [
Track.Source.Microphone, Track.Source.Microphone,
@@ -80,7 +80,7 @@ export function LivekitRoomAudioRenderer({
if (!isValid) { if (!isValid) {
// TODO make sure to also skip the warn logging for the local identity // TODO make sure to also skip the warn logging for the local identity
// Log that there is an invalid identity, that means that someone is publishing audio that is not expected to be in the call. // Log that there is an invalid identity, that means that someone is publishing audio that is not expected to be in the call.
prefixedLogger.warn( logger.warn(
`Audio track ${ref.participant.identity} from ${url} has no matching matrix call member`, `Audio track ${ref.participant.identity} from ${url} has no matching matrix call member`,
`current members: ${validIdentities.join()}`, `current members: ${validIdentities.join()}`,
`track will not get rendered`, `track will not get rendered`,

View File

@@ -94,8 +94,6 @@ declare module "react" {
} }
} }
const logger = rootLogger.getChild("[InCallView]");
export interface ActiveCallProps extends Omit< export interface ActiveCallProps extends Omit<
InCallViewProps, InCallViewProps,
"vm" | "livekitRoom" | "connState" | "footerVm" "vm" | "livekitRoom" | "connState" | "footerVm"
@@ -116,7 +114,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
const mediaDevices = useMediaDevices(); const mediaDevices = useMediaDevices();
const trackProcessorState$ = useTrackProcessorObservable$(); const trackProcessorState$ = useTrackProcessorObservable$();
useEffect(() => { useEffect(() => {
logger.info("START CALL VIEW SCOPE"); rootLogger.info("START CALL VIEW SCOPE");
const scope = new ObservableScope(); const scope = new ObservableScope();
const reactionsReader = new ReactionsReader(scope, props.rtcSession); const reactionsReader = new ReactionsReader(scope, props.rtcSession);
const { autoLeaveWhenOthersLeft, waitForCallPickup, sendNotificationType } = const { autoLeaveWhenOthersLeft, waitForCallPickup, sendNotificationType } =
@@ -218,6 +216,7 @@ export const InCallView: FC<InCallViewProps> = ({
muteStates, muteStates,
onShareClick, onShareClick,
}) => { }) => {
const logger = rootLogger.getChild("[InCallView]");
const { t } = useTranslation(); const { t } = useTranslation();
const { sendReaction, toggleRaisedHand } = useReactionsSender(); const { sendReaction, toggleRaisedHand } = useReactionsSender();

View File

@@ -467,7 +467,7 @@ declare global {
// eslint-disable-next-line no-var, camelcase // eslint-disable-next-line no-var, camelcase
var mx_rage_initStoragePromise: Promise<void> | undefined; var mx_rage_initStoragePromise: Promise<void> | undefined;
} }
export let rageshakeLogger: Logger;
/** /**
* Configure rage shaking support for sending bug reports. * Configure rage shaking support for sending bug reports.
* Modifies globals. * Modifies globals.
@@ -477,7 +477,8 @@ export async function init(): Promise<void> {
global.mx_rage_logger = new ConsoleLogger(); global.mx_rage_logger = new ConsoleLogger();
// configure loglevel based loggers: // configure loglevel based loggers:
setLogExtension(logger, global.mx_rage_logger.log); rageshakeLogger = logger;
setLogExtension(rageshakeLogger, global.mx_rage_logger.log);
// intercept console logging so that we can get matrix_sdk logs: // intercept console logging so that we can get matrix_sdk logs:
// this is nasty, but no logging hooks are provided // this is nasty, but no logging hooks are provided

View File

@@ -39,8 +39,6 @@ import { type Behavior } from "../Behavior";
import { type Epoch, type ObservableScope } from "../ObservableScope"; import { type Epoch, type ObservableScope } from "../ObservableScope";
import { type RoomMemberMap } from "./remoteMembers/MatrixMemberMetadata"; import { type RoomMemberMap } from "./remoteMembers/MatrixMemberMetadata";
const logger = rootLogger.getChild("[CallNotificationLifecycle]");
export type AutoLeaveReason = "allOthersLeft" | "timeout" | "decline"; export type AutoLeaveReason = "allOthersLeft" | "timeout" | "decline";
export interface RingAttempt { export interface RingAttempt {
@@ -114,6 +112,7 @@ export function createCallNotificationLifecycle$({
*/ */
autoLeave$: Observable<AutoLeaveReason>; autoLeave$: Observable<AutoLeaveReason>;
} { } {
const logger = rootLogger.getChild("[CallNotificationLifecycle]");
let ringAttempts$: Observable<RingAttempt> = NEVER; let ringAttempts$: Observable<RingAttempt> = NEVER;
if (options.waitForCallPickup) if (options.waitForCallPickup)
ringAttempts$ = sentCallNotification$.pipe( ringAttempts$ = sentCallNotification$.pipe(

View File

@@ -40,7 +40,7 @@ import {
timer, timer,
takeUntil, takeUntil,
} from "rxjs"; } from "rxjs";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { import {
MembershipManagerEvent, MembershipManagerEvent,
type LivekitTransportConfig, type LivekitTransportConfig,
@@ -157,7 +157,6 @@ import {
} from "../media/RingingMediaViewModel.ts"; } from "../media/RingingMediaViewModel.ts";
import { type GridTileViewModel } from "../TileViewModel.ts"; import { type GridTileViewModel } from "../TileViewModel.ts";
const logger = rootLogger.getChild("[CallViewModel]");
//TODO //TODO
// Larger rename // Larger rename
// member,membership -> rtcMember // member,membership -> rtcMember
@@ -411,6 +410,7 @@ export function createCallViewModel$(
reactionsSubject$: Observable<Record<string, ReactionInfo>>, reactionsSubject$: Observable<Record<string, ReactionInfo>>,
trackProcessorState$: Behavior<ProcessorState>, trackProcessorState$: Behavior<ProcessorState>,
): CallViewModel { ): CallViewModel {
const logger = rootLogger.getChild("[CallViewModel]");
const client = matrixRoom.client; const client = matrixRoom.client;
const userId = client.getUserId(); const userId = client.getUserId();
const deviceId = client.getDeviceId(); const deviceId = client.getDeviceId();
@@ -420,6 +420,7 @@ export function createCallViewModel$(
const livekitKeyProvider = getE2eeKeyProvider( const livekitKeyProvider = getE2eeKeyProvider(
options.encryptionSystem, options.encryptionSystem,
matrixRTCSession, matrixRTCSession,
logger,
); );
// matrix_rtc_mode in config.json overrides the user's Developer Settings choice. // matrix_rtc_mode in config.json overrides the user's Developer Settings choice.
// It is validated at config load (src/config/Config.ts) so the cast is safe. // It is validated at config load (src/config/Config.ts) so the cast is safe.
@@ -1797,6 +1798,7 @@ export function createCallViewModel$(
function getE2eeKeyProvider( function getE2eeKeyProvider(
e2eeSystem: EncryptionSystem, e2eeSystem: EncryptionSystem,
rtcSession: MatrixRTCSession, rtcSession: MatrixRTCSession,
logger: Logger,
): BaseKeyProvider | undefined { ): BaseKeyProvider | undefined {
if (e2eeSystem.kind === E2eeType.NONE) return undefined; if (e2eeSystem.kind === E2eeType.NONE) return undefined;

View File

@@ -31,11 +31,6 @@ import { type ObservableScope } from "../../ObservableScope";
import { type Behavior } from "../../Behavior"; import { type Behavior } from "../../Behavior";
import { type NodeStyleEventEmitter } from "../../../utils/test"; import { type NodeStyleEventEmitter } from "../../../utils/test";
/**
* Logger instance (scoped child) for homeserver connection updates.
*/
const logger = rootLogger.getChild("[HomeserverConnected]");
export type HomeserverDisconnectReason = "sync" | "membership" | "probablyLeft"; export type HomeserverDisconnectReason = "sync" | "membership" | "probablyLeft";
export interface HomeserverConnected { export interface HomeserverConnected {
@@ -70,6 +65,7 @@ export function createHomeserverConnected$(
Pick<MatrixRTCSession, "membershipStatus" | "probablyLeft">, Pick<MatrixRTCSession, "membershipStatus" | "probablyLeft">,
gracePeriodMs?: number, gracePeriodMs?: number,
): HomeserverConnected { ): HomeserverConnected {
const logger = rootLogger.getChild("[HomeserverConnected]");
// Get grace period from parameter or config (default 10000ms) // Get grace period from parameter or config (default 10000ms)
const graceMs = gracePeriodMs ?? Config.get().sync_disconnect_grace_period_ms; const graceMs = gracePeriodMs ?? Config.get().sync_disconnect_grace_period_ms;

View File

@@ -25,7 +25,7 @@ import {
switchMap, switchMap,
tap, tap,
} from "rxjs"; } from "rxjs";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
@@ -46,8 +46,6 @@ import { areLivekitTransportsEqual } from "../remoteMembers/MatrixLivekitMembers
import { customLivekitUrl } from "../../../settings/settings.ts"; import { customLivekitUrl } from "../../../settings/settings.ts";
import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts"; import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
const logger = rootLogger.getChild("[LocalTransport]");
/* /*
* It figures out “which LiveKit focus URL/alias the local user should use,” * It figures out “which LiveKit focus URL/alias the local user should use,”
* optionally aligning with the oldest member, and ensures the SFU path is primed * optionally aligning with the oldest member, and ensures the SFU path is primed
@@ -140,9 +138,14 @@ export const createLocalTransport$ = ({
forceJwtEndpoint, forceJwtEndpoint,
delayId$, delayId$,
}: Props): LocalTransport => { }: Props): LocalTransport => {
const logger = rootLogger.getChild("[LocalTransport]");
// The LiveKit transport in use by the oldest RTC membership. `null` when the // The LiveKit transport in use by the oldest RTC membership. `null` when the
// oldest member has no such transport. // oldest member has no such transport.
const oldestMemberTransport$ = observerOldestMembership$(scope, memberships$); const oldestMemberTransport$ = observerOldestMembership$(
scope,
memberships$,
logger,
);
const transportDiscovery = new RtcTransportAutoDiscovery({ const transportDiscovery = new RtcTransportAutoDiscovery({
client: client, client: client,
@@ -190,6 +193,7 @@ export const createLocalTransport$ = ({
roomId, roomId,
client, client,
delayId ?? undefined, delayId ?? undefined,
logger,
); );
} catch (e) { } catch (e) {
logger.error( logger.error(
@@ -209,6 +213,7 @@ export const createLocalTransport$ = ({
client, client,
ownMembershipIdentity, ownMembershipIdentity,
roomId, roomId,
logger,
); );
} }
@@ -248,6 +253,7 @@ export const createLocalTransport$ = ({
function observerOldestMembership$( function observerOldestMembership$(
scope: ObservableScope, scope: ObservableScope,
memberships$: Behavior<Epoch<CallMembership[]>>, memberships$: Behavior<Epoch<CallMembership[]>>,
logger: Logger,
): Behavior<LivekitTransportConfig | null> { ): Behavior<LivekitTransportConfig | null> {
return scope.behavior<LivekitTransportConfig | null>( return scope.behavior<LivekitTransportConfig | null>(
memberships$.pipe( memberships$.pipe(
@@ -307,6 +313,7 @@ async function doOpenIdAndJWTFromUrl(
> & > &
OpenIDClientParts, OpenIDClientParts,
delayId?: string, delayId?: string,
logger?: Logger,
): Promise<LocalTransportWithSFUConfig> { ): Promise<LocalTransportWithSFUConfig> {
const sfuConfig = await getSFUConfigWithOpenID( const sfuConfig = await getSFUConfigWithOpenID(
client, client,
@@ -337,6 +344,7 @@ function observeLocalTransportForOldestMembership(
OpenIDClientParts, OpenIDClientParts,
ownMembershipIdentity: CallMembershipIdentityParts, ownMembershipIdentity: CallMembershipIdentityParts,
roomId: string, roomId: string,
logger: Logger,
): LocalTransport { ): LocalTransport {
// Ensure we can authenticate with the SFU. // Ensure we can authenticate with the SFU.
const authenticatedOldestMemberTransport$ = oldestMemberTransport$.pipe( const authenticatedOldestMemberTransport$ = oldestMemberTransport$.pipe(
@@ -355,6 +363,7 @@ function observeLocalTransportForOldestMembership(
roomId, roomId,
client, client,
undefined, undefined,
logger,
), ),
).pipe( ).pipe(
catchError((e: unknown) => { catchError((e: unknown) => {

View File

@@ -22,8 +22,6 @@ import {
} from "../../../utils/displayname"; } from "../../../utils/displayname";
import { type Behavior } from "../../Behavior"; import { type Behavior } from "../../Behavior";
const logger = rootLogger.getChild("[MatrixMemberMetadata]");
export type RoomMemberMap = Map< export type RoomMemberMap = Map<
string, string,
Pick<RoomMember, "userId" | "getMxcAvatarUrl" | "rawDisplayName"> Pick<RoomMember, "userId" | "getMxcAvatarUrl" | "rawDisplayName">
@@ -67,6 +65,7 @@ export const memberDisplaynames$ = (
memberships$: Behavior<Pick<CallMembership, "userId">[]>, memberships$: Behavior<Pick<CallMembership, "userId">[]>,
roomMembers$: Behavior<RoomMemberMap>, roomMembers$: Behavior<RoomMemberMap>,
): Behavior<Map<string, string>> => { ): Behavior<Map<string, string>> => {
const logger = rootLogger.getChild("[MatrixMemberMetadata]");
// This map tracks userIds that at some point needed disambiguation. // This map tracks userIds that at some point needed disambiguation.
// This is a memory leak bound to the number of participants. // This is a memory leak bound to the number of participants.
// A call application will always increase the memory if there have been more members in a call. // A call application will always increase the memory if there have been more members in a call.