SlideShare a Scribd company logo
Session Replay
Lessons Learned from Building
a DOM capturing product
Wey Wey Web 2023 Francesco Novy
1
About Me
Sentry & Session Replay
How does Session Replay work?
Lessons Learned:
Know your Use Case
Expect the Unexpected
How to Optimize Bundle Size
How to Compress Data
Overview
2
Francesco Novy
hello@fnovy.com
www.fnovy.com
mydea
Living in Vienna, Austria
8+ years of building web UIs
At Sentry since 2022
Working on the JavaScript SDKs
3
4
Error Monitoring
Performance Monitoring
Profiling
Session Replay
What does Sentry do?
5
Capture what's happening in the user's
browser
See what happened leading up to an
error
In-depth debugging similar to the
Browser DevTools
Session Replay
6
Session Replay
7
Session Replay
8
import * as Sentry from '@sentry/browser';
Sentry.init({
integrations: [
new Sentry.Replay({
unmask: ['.show-this-class']
})
],
// Always capture when an error happens
replaysOnErrorSampleRate: 1.0,
// Capture 10% of sessions generally
replaysSessionSampleRate: 0.1,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
How to use Session Replay?
9
We use a fork of
Mutation Observers
Monkey Patching* Stylesheet APIs
Monkey Patching* Canvas APIs
rrweb
How does it work?
10
* What is Monkey Patching?
const originalFetch = window.fetch;
window.fetch = function() {
console.log("A fetch happened!");
return originalFetch.apply(window, arguments);
}
// Later somewhere in the application
window.fetch('https://example.com');
// Will show the console log
1
2
3
4
5
6
7
8
9
"Monkey patching is a technique used to dynamically
update the behavior of a piece of code at run-time."
11
Know your Use Case
Expect the Unexpected
How to Optimize Bundle Size
How to Compress Data
Lessons Learned
12
Know your Use Case
Do we really need that?
13
Know your Use Case
What do you really need?
Hide text & user input by default
Opt-in to show certain text
Defaults matter: Make the best
way the easy way
Make the worst things impossible
14
Expect the Unexpected
Everything that can go wrong, will go wrong.
15
Expect the Unexpected
Low Level APIs are dangerous to tinker with
Monkey Patching is dangerous
Browser Extensions can do anything
try-catch everything
16
How to Optimize Bundle Size
Ship as little code as necessary.
17
Session Replay Bundle Size
Bundle Size v7.73.0: ~53 KB
Bundle Size v7.78.0: ~35 KB
Still large 😱
... but how?
18
Optimizing Bundle Size
Remove unused code from rrweb
Audit dependencies
Make certain recording features
opt-in
Optimize for Tree Shaking!
19
What is Tree Shaking?
describes the ability to
automatically remove unused code from
your build.
When code is written in a tree-shakeable
way, bundlers like Webpack can optimize
your application based on what is actually
used.
Tree Shaking
20
Tree Shaking: Simple Example
// SDK
import { large, small } from './my-code';
export function largeOrSmall(config) {
return config.useLage ? large() : small();
}
// Application
import { largeOrSmall } from 'sdk';
largeOrSmall({ useLarge: false });
1
2
3
4
5
6
7
8
9
10
11
❌Not tree shakeable
21
// SDK
export { large, small } from './my-code';
// Application
import { small } from 'sdk';
small();
1
2
3
4
5
6
7
✅Tree shakeable
Tree Shaking: Simple Example
22
// SDK
import {
CanvasManager
} from './canvas-manager';
export function record(options) {
if (options.recordCanvas) {
new CanvasManager();
}
}
// Application
import { record } from 'sdk';
record({ recordCanvas: false });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❌Not tree shakeable
Tree Shaking: Actual Example
23
// Application A
import {
record
} from 'sdk';
record({ getCanvasManager: undefined });
1
2
3
4
5
6
✅Tree shakeable
Tree Shaking: Actual Example
// SDK
import {
CanvasManager
} from './canvas-manager';
export function getCanvasManager() {
return new CanvasManager();
}
export function record(options) {
if (options.getCanvasManager) {
options.getCanvasManager();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Application B
import {
record,
getCanvasManager
} from 'sdk';
record({ getCanvasManager });
1
2
3
4
5
6
7
24
How to Compress Data
Avoid unnecessary network traffic, where possible.
25
Compressing Data
Compress data in a web worker
Gracefully handle errors
Make sure to compare libraries
(e.g. )
fflate
26
Web Workers
Setting up the web worker
// worker.js
import { compressSync } from 'fflate';
function handleMessage(e) {
const { input, id } = e.data;
const compressed = compressSync(input);
// Send compressed data back to main thread
postMessage({ id, output: compressed });
}
// Receive uncompressed data from main thread
addEventListener('message', handleMessage);
1
2
3
4
5
6
7
8
9
10
11
12
27
Web Workers
Using the web worker from your application
// Application
const worker = new Worker('/worker.js');
function compressData(data) {
const id = generateUuid();
return new Promise(function (resolve) {
function listener(response) {
if (response.data.id === id) {
worker.removeEventListener('message', listener);
resolve(response.data.output);
}
}
worker.addEventListener('message', listener);
worker.postMessage({ id, input: data });
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
28
https://github.com/getsentry/sentry-javascript
The Sentry SDK is Open
Source!
Everything we do is open source!
Look at the code, PRs, etc.
We love feedback!
29
Thank you!
hello@fnovy.com
www.fnovy.com
mydea
Francesco Novy
30

More Related Content

Lessons Learned from building Session Replay - Francesco Novy

  • 1. Session Replay Lessons Learned from Building a DOM capturing product Wey Wey Web 2023 Francesco Novy 1
  • 2. About Me Sentry & Session Replay How does Session Replay work? Lessons Learned: Know your Use Case Expect the Unexpected How to Optimize Bundle Size How to Compress Data Overview 2
  • 3. Francesco Novy hello@fnovy.com www.fnovy.com mydea Living in Vienna, Austria 8+ years of building web UIs At Sentry since 2022 Working on the JavaScript SDKs 3
  • 4. 4
  • 6. Capture what's happening in the user's browser See what happened leading up to an error In-depth debugging similar to the Browser DevTools Session Replay 6
  • 9. import * as Sentry from '@sentry/browser'; Sentry.init({ integrations: [ new Sentry.Replay({ unmask: ['.show-this-class'] }) ], // Always capture when an error happens replaysOnErrorSampleRate: 1.0, // Capture 10% of sessions generally replaysSessionSampleRate: 0.1, }); 1 2 3 4 5 6 7 8 9 10 11 12 13 How to use Session Replay? 9
  • 10. We use a fork of Mutation Observers Monkey Patching* Stylesheet APIs Monkey Patching* Canvas APIs rrweb How does it work? 10
  • 11. * What is Monkey Patching? const originalFetch = window.fetch; window.fetch = function() { console.log("A fetch happened!"); return originalFetch.apply(window, arguments); } // Later somewhere in the application window.fetch('https://example.com'); // Will show the console log 1 2 3 4 5 6 7 8 9 "Monkey patching is a technique used to dynamically update the behavior of a piece of code at run-time." 11
  • 12. Know your Use Case Expect the Unexpected How to Optimize Bundle Size How to Compress Data Lessons Learned 12
  • 13. Know your Use Case Do we really need that? 13
  • 14. Know your Use Case What do you really need? Hide text & user input by default Opt-in to show certain text Defaults matter: Make the best way the easy way Make the worst things impossible 14
  • 15. Expect the Unexpected Everything that can go wrong, will go wrong. 15
  • 16. Expect the Unexpected Low Level APIs are dangerous to tinker with Monkey Patching is dangerous Browser Extensions can do anything try-catch everything 16
  • 17. How to Optimize Bundle Size Ship as little code as necessary. 17
  • 18. Session Replay Bundle Size Bundle Size v7.73.0: ~53 KB Bundle Size v7.78.0: ~35 KB Still large 😱 ... but how? 18
  • 19. Optimizing Bundle Size Remove unused code from rrweb Audit dependencies Make certain recording features opt-in Optimize for Tree Shaking! 19
  • 20. What is Tree Shaking? describes the ability to automatically remove unused code from your build. When code is written in a tree-shakeable way, bundlers like Webpack can optimize your application based on what is actually used. Tree Shaking 20
  • 21. Tree Shaking: Simple Example // SDK import { large, small } from './my-code'; export function largeOrSmall(config) { return config.useLage ? large() : small(); } // Application import { largeOrSmall } from 'sdk'; largeOrSmall({ useLarge: false }); 1 2 3 4 5 6 7 8 9 10 11 ❌Not tree shakeable 21
  • 22. // SDK export { large, small } from './my-code'; // Application import { small } from 'sdk'; small(); 1 2 3 4 5 6 7 ✅Tree shakeable Tree Shaking: Simple Example 22
  • 23. // SDK import { CanvasManager } from './canvas-manager'; export function record(options) { if (options.recordCanvas) { new CanvasManager(); } } // Application import { record } from 'sdk'; record({ recordCanvas: false }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ❌Not tree shakeable Tree Shaking: Actual Example 23
  • 24. // Application A import { record } from 'sdk'; record({ getCanvasManager: undefined }); 1 2 3 4 5 6 ✅Tree shakeable Tree Shaking: Actual Example // SDK import { CanvasManager } from './canvas-manager'; export function getCanvasManager() { return new CanvasManager(); } export function record(options) { if (options.getCanvasManager) { options.getCanvasManager(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Application B import { record, getCanvasManager } from 'sdk'; record({ getCanvasManager }); 1 2 3 4 5 6 7 24
  • 25. How to Compress Data Avoid unnecessary network traffic, where possible. 25
  • 26. Compressing Data Compress data in a web worker Gracefully handle errors Make sure to compare libraries (e.g. ) fflate 26
  • 27. Web Workers Setting up the web worker // worker.js import { compressSync } from 'fflate'; function handleMessage(e) { const { input, id } = e.data; const compressed = compressSync(input); // Send compressed data back to main thread postMessage({ id, output: compressed }); } // Receive uncompressed data from main thread addEventListener('message', handleMessage); 1 2 3 4 5 6 7 8 9 10 11 12 27
  • 28. Web Workers Using the web worker from your application // Application const worker = new Worker('/worker.js'); function compressData(data) { const id = generateUuid(); return new Promise(function (resolve) { function listener(response) { if (response.data.id === id) { worker.removeEventListener('message', listener); resolve(response.data.output); } } worker.addEventListener('message', listener); worker.postMessage({ id, input: data }); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 28
  • 29. https://github.com/getsentry/sentry-javascript The Sentry SDK is Open Source! Everything we do is open source! Look at the code, PRs, etc. We love feedback! 29