Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Log TimelinePanel debugging info when opening the bug report modal #8502

Merged
merged 13 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
94 changes: 93 additions & 1 deletion src/components/structures/TimelinePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,69 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
}

/**
* Logs out debug info to describe the state of the TimelinePanel and the
* events in the room according to the matrix-js-sdk. This is useful when
* debugging problems like messages out of order, or messages that should
* not be showing up in a thread, etc.
*
* It's too expensive and cumbersome to do all of these calculations for
* every message change so instead we only log it out when asked.
*/
private onDumpDebugLogs = (): void => {
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
const roomId = this.props.timelineSet.room?.roomId;
// Get a list of the event IDs used in this TimelinePanel.
// This includes state and hidden events which we don't render
const eventIdList = this.state.events.map((ev) => ev.getId());

// Get the list of actually rendered events seen in the DOM.
// This is useful to know for sure what's being shown on screen.
// And we can suss out any corrupted React `key` problems.
let renderedEventIds: string[];
const messagePanel = this.messagePanel.current;
if (messagePanel) {
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
if (messagePanelNode) {
const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]');
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
return renderedEvent.getAttribute('data-event-id');
});
}
}

// Get the list of events and threads for the room as seen by the
// matrix-js-sdk.
let serializedEventIdsFromTimelineSets: { [key: string]: string[] }[];
let serializedEventIdsFromThreadsTimelineSets: { [key: string]: string[] }[];
const serializedThreadsMap: { [key: string]: string[] } = {};
const client = MatrixClientPeg.get();
const room = client?.getRoom(roomId);
if (room) {
const timelineSets = room.getTimelineSets();
const threadsTimelineSets = room.threadsTimelineSets;

// Serialize all of the timelineSets and timelines in each set to their event IDs
serializedEventIdsFromTimelineSets = serializeEventIdsFromTimelineSets(timelineSets);
serializedEventIdsFromThreadsTimelineSets = serializeEventIdsFromTimelineSets(threadsTimelineSets);

// Serialize all threads in the room from theadId -> event IDs in the thread
room.getThreads().forEach((thread) => {
serializedThreadsMap[thread.id] = thread.events.map(ev => ev.getId());
});
}

logger.debug(
`TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${roomId}\n` +
`\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
`\trenderedEventIds(${renderedEventIds ? renderedEventIds.length : 0})=` +
`${JSON.stringify(renderedEventIds)}\n` +
`\tserializedEventIdsFromTimelineSets=${JSON.stringify(serializedEventIdsFromTimelineSets)}\n` +
`\tserializedEventIdsFromThreadsTimelineSets=` +
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confusingly, room.threadsTimelineSets-> serializedEventIdsFromThreadsTimelineSets are only created and used for the ThreadPanel where threads are listed not an individual thread.

Thought it was relevant to log but could also be dropped or moved to ThreadPanel in the future instead.

`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}`,
);
Copy link
Contributor Author

@MadLittleMods MadLittleMods May 5, 2022

Choose a reason for hiding this comment

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

These logs look like:

TimelinePanel(Room): Debugging info for !ifHvTosMVvhbsCtKis:my.synapse.server
	events(12)=["$DTQzlOAB9zGXQ--Y9zPo9YK0KdMEeIMJL9kFPf9QE0U","$j_i8bX6Tid06awSNOeWCyYqst0wSCPJGguSX9iUGSWk","$6ZR11pNK8fPt5_TzwwGZEgopaJLgdEChB55ivlIqfDo","$lmgREseqLT0SINbDnE7hpkccIcuWGRUNh7g1utnaq0c","$d9lV1zkkxJtoJzMyvDVa9LyXZA7YwYhUlm9n30jqNI8","$pwd02EGS2HDdvwVn5EKaQEqANf49QFB-OJiOpGkCMB8","$QO5e_sIq83DM8JySRiB0-32lwVrNpReUv33UZ8rGRGo","$Rhe9LSKBgf49ejxKNN7N5ve2Nzqykr0EAtq4hnKMWiA","$t14H6JO0200h9z6_BYRykEDWvZhhNTTt50t5Ex2g_2o","$WeO6B-ea3FxTokjFixc0UghC0osRSP6vykVVwhHbJhw","$BWFwOdh4QmmUpEHNdjAYXEXM5jue3ZcTtLKhc7YIusw","$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY"]
	renderedEventIds(6)=["$DTQzlOAB9zGXQ--Y9zPo9YK0KdMEeIMJL9kFPf9QE0U","$Rhe9LSKBgf49ejxKNN7N5ve2Nzqykr0EAtq4hnKMWiA","$t14H6JO0200h9z6_BYRykEDWvZhhNTTt50t5Ex2g_2o","$WeO6B-ea3FxTokjFixc0UghC0osRSP6vykVVwhHbJhw","$BWFwOdh4QmmUpEHNdjAYXEXM5jue3ZcTtLKhc7YIusw","$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY"]
	serializedEventIdsFromTimelineSets=[{"liveTimeline":["$DTQzlOAB9zGXQ--Y9zPo9YK0KdMEeIMJL9kFPf9QE0U","$j_i8bX6Tid06awSNOeWCyYqst0wSCPJGguSX9iUGSWk","$6ZR11pNK8fPt5_TzwwGZEgopaJLgdEChB55ivlIqfDo","$lmgREseqLT0SINbDnE7hpkccIcuWGRUNh7g1utnaq0c","$d9lV1zkkxJtoJzMyvDVa9LyXZA7YwYhUlm9n30jqNI8","$pwd02EGS2HDdvwVn5EKaQEqANf49QFB-OJiOpGkCMB8","$QO5e_sIq83DM8JySRiB0-32lwVrNpReUv33UZ8rGRGo","$Rhe9LSKBgf49ejxKNN7N5ve2Nzqykr0EAtq4hnKMWiA","$t14H6JO0200h9z6_BYRykEDWvZhhNTTt50t5Ex2g_2o","$WeO6B-ea3FxTokjFixc0UghC0osRSP6vykVVwhHbJhw","$BWFwOdh4QmmUpEHNdjAYXEXM5jue3ZcTtLKhc7YIusw","$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY"]}]
	serializedEventIdsFromThreadsTimelineSets=[]
	serializedThreadsMap={"$BWFwOdh4QmmUpEHNdjAYXEXM5jue3ZcTtLKhc7YIusw":[],"$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY":["$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY","$c-6O4zVYEpEuXU5bWfqFtJXXKXARxJDn64BO_9HnP4A"]}
	
TimelinePanel(Thread): Debugging info for !ifHvTosMVvhbsCtKis:my.synapse.server
	events(2)=["$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY","$c-6O4zVYEpEuXU5bWfqFtJXXKXARxJDn64BO_9HnP4A"]
	renderedEventIds(2)=["$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY","$c-6O4zVYEpEuXU5bWfqFtJXXKXARxJDn64BO_9HnP4A"]
	serializedEventIdsFromTimelineSets=[{"liveTimeline":["$DTQzlOAB9zGXQ--Y9zPo9YK0KdMEeIMJL9kFPf9QE0U","$j_i8bX6Tid06awSNOeWCyYqst0wSCPJGguSX9iUGSWk","$6ZR11pNK8fPt5_TzwwGZEgopaJLgdEChB55ivlIqfDo","$lmgREseqLT0SINbDnE7hpkccIcuWGRUNh7g1utnaq0c","$d9lV1zkkxJtoJzMyvDVa9LyXZA7YwYhUlm9n30jqNI8","$pwd02EGS2HDdvwVn5EKaQEqANf49QFB-OJiOpGkCMB8","$QO5e_sIq83DM8JySRiB0-32lwVrNpReUv33UZ8rGRGo","$Rhe9LSKBgf49ejxKNN7N5ve2Nzqykr0EAtq4hnKMWiA","$t14H6JO0200h9z6_BYRykEDWvZhhNTTt50t5Ex2g_2o","$WeO6B-ea3FxTokjFixc0UghC0osRSP6vykVVwhHbJhw","$BWFwOdh4QmmUpEHNdjAYXEXM5jue3ZcTtLKhc7YIusw","$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY"]}]
	serializedEventIdsFromThreadsTimelineSets=[]
	serializedThreadsMap={"$BWFwOdh4QmmUpEHNdjAYXEXM5jue3ZcTtLKhc7YIusw":[],"$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY":["$VknYvjxNw2HKifHp_8C5F_WqDMXBTfdDYYT3-AVnNhY","$c-6O4zVYEpEuXU5bWfqFtJXXKXARxJDn64BO_9HnP4A"]}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there any other info that would be useful to have?

};

private onMessageListUnfillRequest = (backwards: boolean, scrollToken: string): void => {
// If backwards, unpaginate from the back (i.e. the start of the timeline)
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
Expand Down Expand Up @@ -529,6 +592,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
case "ignore_state_changed":
this.forceUpdate();
break;
case Action.DumpDebugLogs:
this.onDumpDebugLogs();
break;
}
};

Expand Down Expand Up @@ -1465,7 +1531,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
const messagePanel = this.messagePanel.current;
if (!messagePanel) return null;

const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as HTMLElement;
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
if (!messagePanelNode) return null; // sometimes this happens for fresh rooms/post-sync
const wrapperRect = messagePanelNode.getBoundingClientRect();
const myUserId = MatrixClientPeg.get().credentials.userId;
Expand Down Expand Up @@ -1687,4 +1753,30 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
}

/**
* Iterate across all of the timelineSets and timelines inside to expose all of
* the event IDs contained inside.
*
* @return An event ID list for every timeline in every timelineSet
*/
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] {
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
const timelineMap = {};

const timelines = timelineSet.getTimelines();
const liveTimeline = timelineSet.getLiveTimeline();

timelines.forEach((timeline, index) => {
// Add a special label when it is the live timeline so we can tell
// it apart from the others
const isLiveTimeline = timeline === liveTimeline;
timelineMap[isLiveTimeline ? 'liveTimeline' : `${index}`] = timeline.getEvents().map(ev => ev.getId());
});

return timelineMap;
});

return serializedEventIdsInTimelineSet;
}

export default TimelinePanel;
12 changes: 12 additions & 0 deletions src/components/views/dialogs/BugReportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import Field from '../elements/Field';
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
import { sendSentryReport } from "../../../sentry";
import defaultDispatcher from '../../../dispatcher/dispatcher';
import { Action } from '../../../dispatcher/actions';

interface IProps {
onFinished: (success: boolean) => void;
Expand Down Expand Up @@ -65,6 +67,16 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
downloadProgress: null,
};
this.unmounted = false;

// Get all of the extra info dumped to the console when someone is about
// to send debug logs. Since this is a fire and forget action, we do
// this when the bug report dialog is opened instead of when we submit
// logs because we have no signal to know when all of the various
// components have finished logging. Someone could potentially send logs
// before we fully dump everything but it's probably unlikely.
defaultDispatcher.dispatch({
action: Action.DumpDebugLogs,
});
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
}

public componentWillUnmount() {
Expand Down
2 changes: 2 additions & 0 deletions src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1291,6 +1291,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
"data-has-reply": !!replyChain,
"data-layout": this.props.layout,
"data-self": isOwnEvent,
"data-event-id": this.props.mxEvent.getId(),
"onMouseEnter": () => this.setState({ hover: true }),
"onMouseLeave": () => this.setState({ hover: false }),
}, [
Expand Down Expand Up @@ -1437,6 +1438,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
"data-scroll-tokens": scrollToken,
"data-layout": this.props.layout,
"data-self": isOwnEvent,
"data-event-id": this.props.mxEvent.getId(),
"data-has-reply": !!replyChain,
"onMouseEnter": () => this.setState({ hover: true }),
"onMouseLeave": () => this.setState({ hover: false }),
Expand Down
7 changes: 7 additions & 0 deletions src/dispatcher/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,11 @@ export enum Action {
* Opens a dialog to add an existing object to a space. Used with a OpenAddExistingToSpaceDialogPayload.
*/
OpenAddToExistingSpaceDialog = "open_add_to_existing_space_dialog",

/**
* Let components know that they should log any useful debugging information
* because we're probably about to send bug report which includes all of the
* logs.
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
*/
DumpDebugLogs = "dump_debug_logs",
}