Power Platform: Monitoring a power app

When building apps in Power Apps, understanding what happens behind the scenes is essential for smooth performance and reliable functionality. The Monitor tool provides a powerful way to observe your app in action, offering real-time insights into its behavior and interactions.

Monitor acts as a live event viewer, capturing everything from data operations to user-triggered actions. It helps makers identify issues quickly, optimize performance, and validate logic without guesswork. Whether you’re troubleshooting a connector, analyzing load times, or verifying formula execution, Monitor gives you the visibility you need.

With Monitor, you can capture:

  • Performance metrics (load times, data refresh times)
  • Function calls (e.g., ClearCollect, Patch, Navigate)
  • Network requests (to SharePoint, Dataverse, APIs)
  • Errors and warnings
  • Trace logs (from Trace() statements)

How to use the monitor tool

  • In Power Apps Studio, from the app list, select Details → Live monitor under the app’s menu (3 dots).
  • The Monitor window will open for your app.
  • Click Play published app to run the app. It will open in a new browser tab.
  • Use the app as you normally would, focusing on the parts you want to monitor.
  • Events will appear live in the Monitor window as you interact with the app.
  • Use filters to narrow down event types such as Errors, Warnings, or Trace messages.
  • Click any event to view detailed information, including formula execution, data payload, and timing.

How to use Trace()

The Trace function allows you to send custom messages to the Monitor log while your app runs. This is useful for debugging and understanding the flow of your app. You can log simple text messages, variable values, or even complex objects and collections. You can also specify the severity of the message, such as Information, Warning, or Error. Trace is only available in canvas apps and does not work in model-driven apps.

To send a message only when a specific condition is met, wrap the Trace function inside an If statement. This helps you log errors or warnings only when they occur, instead of flooding the log with unnecessary messages.

Trace("Start of OnScreen"); // Manual text message

Trace("Value of varCompHeigh" & varCompHeight); // A variable

Trace("Value of gblAppColors" & JSON(gblAppColors)); // An object as JSON

Trace("Trace Warning", TraceSeverity.Warning); // Trace of type warning

Trace("Trace Critical", TraceSeverity.Critical); // Trace of type Critical

Trace("Trace Error", TraceSeverity.Error); // Trace of type Error

Trace("Trace Information", TraceSeverity.Information); // Trace of type Information

If(IsEmpty(colDesks), Trace("Collection is empty: " & JSON(colDesks), TraceSeverity.Error)) // Trace within If condition

Trace("End of OnStart"); // Manual text message

Power Pages: Tracking clicks and downloads

Knowing how your external users are using you Power Pages is very important. Especially if you are sharing import documents through the Power Pages. In this blog post, we will show you how to set up a Power Page with a script that monitors the downloads and stores this data in Dataverse. By leveraging this functionality, you can gain valuable insights into user behaviour, track file downloads, and make data-driven decisions.

This solution was created together with my colleague Rik de Koning who speaks regularly at Power Platform events.

Power Pages configuration

Before we can save data to the Dataverse with a JavaScript we need to configure the Power Pages to allow the web api access. You need to configure the first 2 settings for each Dataverse table where data needs to be stored.

  • Open the Portal Management of your Power Pages.
  • Go to Site Settings
  • Here you need to add three settings.
  • The first setting is to enable the web api on your Dataverse table.
    • Name: Webapi/[table logical name]/enabled
    • Website: Select the website
    • Value: true
  • The second setting is to allow the web api to access the fields
    • Name: Webapi/[table logical name]/fields
    • Website: Select the website
    • Value: cre9b_userid,cre9b_document,cre9b_username,cre9b_dateandtime
  • The third setting is to allow for errors to be displayed.
    • Name: Webapi/error/innererror
    • Website: Select the website
    • Value: true

The JavaScript

In our example we created a button that both downloads a document (note with an attached file) and creates a new record in a Dataverse table. The api call to create the record is added as a onclick event on the button, while the href is linked to the actual document. The JavaScript itself needs to be added on the content page linked to the Information page. If you have another use case please look at the details of the Portal Web API on Learn Microsoft.

  • Open the Portal Management and open the Web Pages
  • Open the Information page where the JavaScript needs to be added.
  • Open the related Content page.
  • First you will need to add the Wrapper AJAX function, this will give you the function safeAjax to use the web api.
(function(webapi, $){
    function safeAjax(ajaxOptions) {
        var deferredAjax = $.Deferred();

        shell.getTokenDeferred().done(function (token) {
            // add headers for AJAX
            if (!ajaxOptions.headers) {
                $.extend(ajaxOptions, {
                    headers: {
                        "__RequestVerificationToken": token
                    }
                }); 
            } else {
                ajaxOptions.headers["__RequestVerificationToken"] = token;
            }
            $.ajax(ajaxOptions)
                .done(function(data, textStatus, jqXHR) {
                    validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
                }).fail(deferredAjax.reject); //AJAX
        }).fail(function () {
            deferredAjax.rejectWith(this, arguments); // on token failure pass the token AJAX and args
        });

        return deferredAjax.promise();	
    }
    webapi.safeAjax = safeAjax;
})(window.webapi = window.webapi || {}, jQuery);
  • You can now add a function to the script that gathers the required information and send that to the safeAjax funtion. The safeAjax function then executes the api call.
function openfile(url) {
	var nowDateTime = new Date();
	var username = "{{ user.fullname }}";
	var contactid = "{{user.contactid}}";

    webapi.safeAjax({
		type: "POST",
		url: "/_api/[tablename]",
		contentType: "application/json",
		data: JSON.stringify({
			"cre9b_userid": contactid,
			"cre9b_document": url,
			"cre9b_username": username,
			"cre9b_dateandtime": nowDateTime
			
		}),
		success: function (res, status, xhr) {
      //print id of newly created table record
			console.log("entityID: "+ xhr.getResponseHeader("entityid"))
		}
	});
}
  • You can use the {{ user.[column] }} to get data out of the contacts table.
  • url is not a default options, I created a variable in a different part of the JavaScript.

For everybody that is interested in how we added the buttons I have added that code as well. With a JavaScript we created a button for each file (note) by changing the URL column into a button.

$('.entitylist').on("loaded", function () {
    // this will get the index of the corresponding table header
    var columnindex = $('th:contains("URL")').index();
    // this will loop through each table row below the corresponding table header
    $('tr').each(function () {
        var column = $('td', this).eq(columnindex);
        if (column.text().includes("https://")) {
            // this will find all a attributes based on the document links link
            const documentLinks = document.querySelectorAll(`[href="${column.text()}"]`)
            documentLinks.forEach(documentLink => {
                // this will set the className and the text of the link accordingly
                documentLink.className = "btnViewDocument"
                documentLink.target = "_blank"
                documentLink.text = "View"
                documentLink.onclick = function() { openfile(documentLink.href);};
            });
        }
    });
    // this will clear the name of the corresponding table header
    document.querySelector(`[aria-label*="URL"]`).innerHTML = ""
    document.querySelector(`[aria-label*="Created On"]`).innerHTML = "Shared On"
});