Skip to content

Analytics

Dopple uses a “log writer” approach for emitting events and handling analytics data. In a nutshell, you provide a custom log writer function to handle event data however you need, and Dopple will call that log writer function whenever an event occurs.

For example, if a user updates a product’s configuration or launches into AR mode, Dopple will call your log writer function, passing along relevant data about the event to your custom function.

const dopple = new DoppleXR({ /* ... */ });
// Your custom log writer function; automatically called whenever an event is emitted
function customLogWriter(data) {
// Parse and handle the event data as needed
console.log(JSON.parse(data));
return true;
};
// Register your log writer
dopple.addLogWriter(customLogWriter);

To create a custom log writer to handle your events, first define the log writer’s function. This custom log writer function can contain any logic needed for handling event data, such as sending them to your analytics platform of choice.

function customLogWriter(data) {
// Parse and handle the data as needed here
const dataObject = JSON.parse(data);
console.log(dataObject);
// Return `true` to allow the data to propagate to other log writers
return true;
};

data will be a stringified JSON object with the following interface:

interface DataObj {
context: {
logLevel: number; // The level code for the type of log, e.g. 30 = INFO, 50 = ERROR
owner: string;
project: string;
version: string;
workspace: string;
event?: string; // Included automatically for default events, e.g. `LOADED`
namespace?: string; // The `logNamespace` value (if provided when initializing Dopple)
sessionId?: string; // ID for the current user's session (if provided when initializing Dopple)
};
time: number;
message: string;
}

Once created, you can register your custom log writer function with Dopple using dopple.addLogWriter().

dopple.addLogWriter(customLogWriter);

Once a log writer function has been registered, it will be called each time any event gets triggered. Dopple will emit certain default events, or you can emit a log event manually by calling the method for one of the log levels below, such as dopple.logger.info().

By default, Dopple will log events anytime certain actions occur, such as when the product finishes loading or when the product’s selection (configuration) is updated.

All default Dopple events are listed below:

Event NameEmitted When
AR_INITIATEDdopple.startAR() is called.
CAMERA_UPDATEThe camera’s view or position have changed, such as by rotating or zooming.
CANVAS_RESIZEThe Dopple canvas’s size has changed.
IN_VIEWPORTThe Dopple canvas comes into view on the user’s screen.
INITThe dopple instance has begun initializing.
LOADEDThe scene has finished loading its initial assets.
LOADED_MATRIXThe product’s matrix has finished loading.
OUT_OF_VIEWPORTThe Dopple canvas goes out of view of the user’s screen.
SELECTION_UPDATEDdopple.updateSelection() has finished resolving; the new selection has been applied.
SESSION_ENDEDThe Dopple instance has been destroyed by calling dopple[Symbol.dispose]()
SNAPSHOT_TAKENdopple.takeSnapshot() is called.
TEXTURE_UPSCALEDHigher-resolution textures have finished loading in and been applied.
UPDATE_SELECTIONdopple.updateSelection() is called.

All default events contain the same data object structure mentioned earlier, with a few additions for certain events:

  • LOADED has the duration for how long it took to load.
  • CAMERA_UPDATE has the new target and position that the camera was updated to
  • SELECTION_UPDATED and UPDATE_SELECTION have the old and current product selection (i.e. current configuration) objects.

Custom events can be manually triggered at any time by calling the logger’s built-in methods, such as info() or warn().

All logger methods take two arguments:

  1. A context object, with any custom properties of your choice that will be appended to the context object in the data sent to your log writer function.
  2. An optional message string to be sent along with the context data to your log writer function.
dopple.logger.info(
// Context object (containing any data about your event)
{ event: "YOUR_EVENT_NAME", foo: "bar" },
// Message (include any custom description or message with your event; optional)
"Custom event message"
);

Then, you can access this data, along with any custom properties you include, from inside your log writer function/

function customLogWriter(data) {
const { context, message } = JSON.parse(data);
console.log(context?.event); // "YOUR_EVENT_NAME"
console.log(context?.foo); // "bar"
console.log(message); // "Custom event message"
return true;
};

An object containing properties and data to be sent to the logger. The context object is flexible; it can contain any custom properties with custom values you need.

An optional string included alongside the context object in the logger. For simple use cases, you may skip including any context object and instead only pass a message to the logger.

dopple.logger.info("My simple message");

Each log’s context includes a logLevel number, which corresponds with the log method that was used.

For most standard events with Dopple, it is recommended to use dopple.logger.info(); however, other methods exist in case they are needed for more fine-grained event or error logging in rare cases.

LevelMethodDescription
10trace()For verbose, detailed diagnostic info, typically for debugging.
20debug()For logging regular debugging info.
30info()For standard event logging (recommended).
40warn()For any abnormal conditions that aren’t outright failures.
50error()For recoverable failures.
60fatal()For extreme, unrecoverable failures.

For example, calling dopple.logger.warn("Be careful") will pass the following data to your log writer function:

{
content: {
logLevel: 40,
// ...
},
message: "Be careful"
}

In more complex scenarios where you may be logging events across multiple product instances (such as if you have a variety of products, or are testing a product across development/staging/production environments), it may be handy to “group” events by their instance.

Dopple lets you do this in two ways: by specifying either a logNamespace when you first initialize your product’s instance, a sessionId, or both.

const dopple = new DoppleXR({
// ...
logNamespace: "my-custom-log-namespace",
sessionId: window.crypto.randomUUID()
});
  • logNamespace — ideal for creating a namespace or “group” for the event to fall within.
  • sessionId — typically used for keeping track of a user’s session.

If specified, the logNamespace and sessionId values will be included with the context object in each event so that your custom log writer function can handle different cases as needed.

function customLogWriter(data) {
const { context } = JSON.parse(data);
console.log(context.logNamespace); // "my-custom-log-namespace"
console.log(context.sessionId); // "a1a1a1a1-b2b2-c3c3-d4d4-e5e5e5e5e5e5"
};

To begin integrating with Google Analytics:

  1. Add the gtag script to your page.

    <head>
    <!-- Replace with your Measurement ID below -->
    <script src="https://www.googletagmanager.com/gtag/js?id=G-ABCDE12345" async></script>
    </head>
  2. Initialize Google Tag Manager with your Measurement ID.

    function gtag(...args) {
    window.dataLayer.push(args);
    }
    window.gtag = gtag;
    gtag("js", new Date());
    gtag("config", "G-ABCDE12345"); // Replace with your Measurement ID
  3. Define the events you want to capture and send to Google Analytics.

    const EVENT_NAMES = {
    // Default events from Dopple (automatically emitted)
    LOADED: "dopple_configurator_loaded",
    IN_VIEWPORT: "dopple_canvas_shown",
    SNAPSHOT_TAKEN: "dopple_snapshot_taken",
    AR_INITIATED: "dopple_ar_initiated",
    SELECTION_UPDATED: "dopple_property_changed",
    // Custom events (add any custom events here as needed)
    ADD_TO_CART: "dopple_add_to_cart"
    }
  4. Create a log writer function to handle sending Dopple events to Google Analytics.

    function logGAEvent(data) {
    const { context, message, time } = JSON.parse(data);
    // Exit early in case gtag or the event aren't properly set
    if (!window.gtag || !context?.event) {
    return false;
    }
    const eventData = {
    dopple_session_id: context?.sessionId || window.crypto.randomUUID(),
    timestamp: time
    };
    // Exit early if this event isn't one of the ones listed above to be captured
    if (!(context.event in EVENT_NAMES)) {
    return false;
    }
    // Get the display name for the current event to be used in the Google Analytics dashboard
    const eventName = EVENT_NAMES[context.event];
    // Handle sending any newly updated properties and options to Google Analytics
    if (context.event === "SELECTION_UPDATED") {
    const oldSel = context.oldSelection ?? {};
    const newSel = context.selection ?? {};
    for (const key in newSel) {
    if (key in oldSel && newSel[key] !== oldSel[key]) {
    window.gtag("event", eventName, {
    ...eventData,
    property_name: key,
    selected_option: newSel[key]
    });
    break;
    }
    }
    // Exit but allow other log writers to fire for this "SELECTION_UPDATED" event
    return true;
    }
    // Handle including extra data with camera, snapshot, and AR events
    switch (context.event) {
    case "CAMERA_UPDATE":
    // Include the camera's new target and position data in the event data
    Object.assign(eventData, {
    target: context.target,
    position: context.position
    });
    break;
    case "SNAPSHOT_TAKEN":
    case "AR_INITIATED":
    // Include the product's current configuration in the event data
    eventData.full_config_object = context.selection;
    break;
    }
    // Send the event and its payload data to Google Analytics
    window.gtag("event", eventName, eventData);
    // Exit and prevent other log writers from firing for these events
    return false;
    }
  5. Register the Google Analytics log writer function.

    dopple.addLogWriter(logGAEvent);
  6. Add any event listeners or triggers for your custom events as needed.

    For example, if your page features an Add To Cart button, you may call dopple.logger.info() with your custom ADD_TO_CART event whenever that button is clicked.

    <div id="dopple-container"></div>
    <button id="add-to-cart">Add To Cart</button>
    const addToCartButton = document.getElementById("add-to-cart");
    addToCartBtn?.addEventListener("click", () => {
    dopple.logger.info(
    { event: "ADD_TO_CART" },
    "User added the product to the cart",
    );
    });`
  7. That’s it! For reference, the full example code snippet is below.

    <head>
    <!-- Replace with your Measurement ID below -->
    <script src="https://www.googletagmanager.com/gtag/js?id=G-ABCDE12345" async></script>
    </head>
    <body>
    <div id="dopple-container"></div>
    <button id="add-to-cart">Add To Cart</button>
    </body>
    import { DoppleXR } from "https://builds.dopple.io/packages/dopple-sdk@latest/dopple-sdk.js";
    // Initialize Dopple
    const dopple = new DoppleXR({
    container: document.getElementById("dopple-container"),
    owner: "my-org",
    workspace: "my-workspace",
    projectName: "My Project",
    productVersion: "1",
    105 collapsed lines
    selection: {},
    logNamespace: "my-custom-log-namespace",
    sessionId: window.crypto.randomUUID()
    });
    // Set up gtag
    function gtag(...args) {
    window.dataLayer.push(args);
    }
    window.gtag = gtag;
    gtag("js", new Date());
    gtag("config", "G-ABCDE12345"); // Replace with your Measurement ID
    const EVENT_NAMES = {
    // Default events from Dopple (automatically emitted)
    LOADED: "dopple_configurator_loaded",
    IN_VIEWPORT: "dopple_canvas_shown",
    SNAPSHOT_TAKEN: "dopple_snapshot_taken",
    AR_INITIATED: "dopple_ar_initiated",
    SELECTION_UPDATED: "dopple_property_changed",
    // Custom events (add any custom events here as needed)
    ADD_TO_CART: "dopple_add_to_cart"
    }
    // Custom log writer function
    function logGAEvent(data) {
    const { context, message, time } = JSON.parse(data);
    // Exit early in case gtag or the event aren't properly set
    if (!window.gtag || !context?.event) {
    return false;
    }
    const eventData = {
    dopple_session_id: context?.sessionId || window.crypto.randomUUID(),
    timestamp: time
    };
    // Exit early if this event isn't one of the ones listed above to be captured
    if (!(context.event in EVENT_NAMES)) {
    return false;
    }
    // Get the display name for the current event to be used in the Google Analytics dashboard
    const eventName = EVENT_NAMES[context.event];
    // Handle sending any newly updated properties and options to Google Analytics
    if (context.event === "SELECTION_UPDATED") {
    const oldSel = context.oldSelection ?? {};
    const newSel = context.selection ?? {};
    for (const key in newSel) {
    if (key in oldSel && newSel[key] !== oldSel[key]) {
    window.gtag("event", eventName, {
    ...eventData,
    property_name: key,
    selected_option: newSel[key]
    });
    break;
    }
    }
    // Exit but allow other log writers to fire for this "SELECTION_UPDATED" event
    return true;
    }
    // Handle including extra data with camera, snapshot, and AR events
    switch (context.event) {
    case "CAMERA_UPDATE":
    // Include the camera's new target and position data in the event data
    Object.assign(eventData, {
    target: context.target,
    position: context.position
    });
    break;
    case "SNAPSHOT_TAKEN":
    case "AR_INITIATED":
    // Include the product's current configuration in the event data
    eventData.full_config_object = context.selection;
    break;
    }
    // Send the event and its payload data to Google Analytics
    window.gtag("event", eventName, eventData);
    // Exit and prevent other log writers from firing for these events
    return false;
    }
    // Register the custom log writer function
    dopple.addLogWriter(logGAEvent);
    // Load the 3D product
    await dopple.load();
    dopple.run();
    // Send a custom event when Add To Cart is clicked
    const addToCartButton = document.getElementById("add-to-cart");
    addToCartBtn?.addEventListener("click", () => {
    dopple.logger.info(
    { event: "ADD_TO_CART" },
    "User added the product to the cart",
    );
    });`
index.html
<html>
<head>
<script type="module">
import { DoppleXR } from "https://builds.dopple.io/packages/dopple-sdk@latest/dopple-sdk.js";
// Initialize Dopple
const dopple = new DoppleXR({
container: document.getElementById("dopple-container"),
owner: "my-org",
workspace: "my-workspace",
projectName: "My Project",
productVersion: "1",
selection: {},
logNamespace: "my-custom-log-namespace",
sessionId: window.crypto.randomUUID()
});
// Custom log writer function
function customLogWriter(data) {
const dataObject = JSON.parse(data);
switch (dataObject?.context?.logLevel) {
// Handle any errors
case 50:
console.error(dataObject?.message);
break;
// Handle any warnings
case 40:
console.warn(dataObject?.message);
break;
// Send any regular/successful info logs to your analytics platform
case 30:
sendToAnalytics(dataObject);
}
return true;
};
// Register the custom log writer function
dopple.addLogWriter(customLogWriter);
// Example of adding a custom event to the Add To Cart button
const addToCartButton = document.querySelector("#add-to-cart");
addToCartButton.addEventListener("click", () => {
dopple.logger.info(
{
event: "ADD_TO_CART", // Custom name for the event
currency: "USD", // Include metadata for the currency in the log's context object
},
"User added the product to their cart"
);
});
// Dummy function to send data to your analytics platform, such as Google Analytics
function sendToAnalytics(dataObj) {
const { context, message } = dataObj;
// Examples of how to handle unique context data for different events
switch (context?.event) {
case "LOADED":
console.log(`Product loaded in ${context?.duration}ms`);
break;
case "SELECTION_UPDATED":
console.log("New selection:", context?.selection);
break;
case "ADD_TO_CART":
console.log(`${message} (Currency: ${context?.currency})`);
break;
}
// Include any additional logic to send the data to your backend platform here...
}
// Load the 3D product
await dopple.load();
dopple.run();
</script>
</head>
<body>
<div id="dopple-container"></div>
<button id="add-to-cart">Add To Cart</button>
</body>
</html>