title | description | author | ms.topic | ms.devlang | ms.search.keywords | ms.date | ms.author | ms.reviewer |
---|---|---|---|---|---|---|---|---|
Feature telemetry |
Learn about the telemetry that you can emit from features in Business Central. |
bholtorf |
conceptual |
al |
administration, tenant, admin, environment, sandbox, telemetry, data, sensitive |
07/13/2023 |
bholtorf |
bholtorf |
[!INCLUDEazure-ad-to-microsoft-entra-id]
The Telemetry AL module simplifies the way you monitor the health of your app and the uptake and usage of application features. There are multiple benefits of using the module compared to sending telemetry via Session.LogMessage
. For example:
- Different features can be compared across the same metrics.
- Common information is sent together with every feature telemetry message, which allows for advanced filtering capabilities.
If you want to get feature telemetry to the app/extension telemetry pipeline, you must add a codeunit that implements the "Telemetry Logger" interface. Below is a simple example on such a codeunit.
/// <summary>
/// Adds support for the extension to use the "Telemetry" and "Feature Telemetry" codeunits.
/// </summary>
codeunit 50101 "Sample Telemetry Logger" implements "Telemetry Logger"
{
Access = Internal;
procedure LogMessage(EventId: Text; Message: Text; Verbosity: Verbosity; DataClassification: DataClassification; TelemetryScope: TelemetryScope; CustomDimensions: Dictionary of [Text, Text])
begin
Session.LogMessage(EventId, Message, Verbosity, DataClassification, TelemetryScope, CustomDimensions);
end;
// For the functionality to behave as expected, there must be exactly one implementation of the "Telemetry Logger" interface registered per app publisher
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Telemetry Loggers", 'OnRegisterTelemetryLogger', '', true, true)]
local procedure OnRegisterTelemetryLogger(var Sender: Codeunit "Telemetry Loggers")
var
SampleTelemetryLogger: Codeunit "Sample Telemetry Logger";
begin
Sender.Register(SampleTelemetryLogger);
end;
}
Note
If you fail to setup a "Telemetry Logger" codeunit, the [!INCLUDEprod_short] server logs an event to your telemetry. For more information, see Error telemetry for the app publisher below.
It's easy to use the Feature Telemetry codeunit. For example, to register the usage of a feature, you can just use the FeatureTelemetry.LogUsage(<tag>, <feature name>, <event name>);
method. After the telemetry is emitted, you can aggregate and display the data. For example, you can use the Feature Usage Power BI report. The report is available on our BCTech GitHub repository.
:::image type="content" source="../media/FeatureUsageReport.png" alt-text="The FeatureUsage Power BI report":::
There are three kinds of events that you can log through the Feature Telemetry codeunit.
FeatureTelemetry.<LogUsage|LogError|LogUptake>(...)
LogUsage
should be called when the feature is successfully used by a user.LogError
should be called when an error must be explicitly sent to telemetry.LogUptake
should be called when a user changes the uptake state of a feature. There are four uptake states for features:Undiscovered
Discovered
Set up
Used
If a feature logs uptake, there should be calls to register the Discovered
, Set up
, and Used
states. The following list describes the current convention for registering uptake states:
Discovered
should be registered when pages related to the given feature are opened (or when a user looks for information about a feature).Set up
should be registered when the user performed a set up for the feature (usually right after a record in a table related to the feature is added or updated).Used
should be registered when a user attempts to use the feature (note the difference with LogUsage, which should be called only if the feature is used successfully).
Note
Tracking the uptake status of a feature might make database transactions. If LogUptake
is called from within a try function, the PerformWriteTransactionsInASeparateSession
parameter should be set to True
.
Calling LogUptake
when the uptake state is Undiscovered
resets the uptake state of the feature. The telemetry from this call will be used to calculate the values in the uptake funnel of the feature.
If LogUptake
is called from a try function, the PerformWriteTransactionsInASeparateSession
parameter should be set to true
.
You call LogError
when an error must be explicitly sent to telemetry. For example, after a call to a try function, when Codeunit.Run
returned false, when sending an http response error message, and so on.
Note
In case an error happens in a database transactions that will be rolled back, the LogError
will still log an event to telemetry.
See sample KQL code below to get started analyzing error telemetry.
Feature names should be short and easy to identify. For example, Retention policies, Configuration packages, and Emailing. Event names should specify the scenario being executed. If LogUsage
is called, the event name should use the past tense because the event has already happened. For example, Email sent
or Retention policy applied
. If LogError
is called, the event name should use the present tense. For example, Sending email
, Loading template
).
Dimension | Description or value |
---|---|
message | Depends on the event. |
severityLevel | 1 |
user_Id | [!INCLUDEuser_Id] |
Dimension | Description or value |
---|---|
aadTenantId | Specifies the Microsoft Entra tenant ID used for Microsoft Entra authentication. For on-premises, if you aren't using Microsoft Entra authentication, this value is common. |
alCategory | FeatureTelemetry. |
alFeatureName | The name of the feature being tracked. |
alSubCategory | Holds one of the values Uptake, Usage, or Error. |
alFeatureUptakeStatus | If alSubCategory holds the value Uptake, then the update status can hold one of the following values: Discovered, Set up, Undiscovered, or Used. |
alCallerAppId | The id of the extension that emitted telemetry. |
alCallerAppName | The name of the extension that emitted telemetry. |
alCallerPublisher | The publisher of the extension that emitted telemetry. |
alCallerAppVersion | The version of the extension that emitted telemetry. |
alCallerAppVersionMajor | The major version of the extension that emitted telemetry. |
alCallerAppVersionMinor | The minor version of the extension that emitted telemetry. |
alClientType | The client type of the session. |
alCompany | The current company name. |
alIsEvaluationCompany | [!INCLUDEenvironmentType] |
alTenantLicenseState | [!INCLUDEenvironmentType] |
alIsAdmin | [!INCLUDEenvironmentType] |
alCountryCode | [!INCLUDEenvironmentType] |
alUserRole | The profile ID associated with the user in the User Personalization table. |
environmentName | Specifies the name of the tenant environment. See Managing Environments. This dimension isn't included for [!INCLUDEprod_short] on-premises environments. |
environmentType | Specifies the environment type for the tenant, such as Production, Sandbox, Trial. See Environment Types. |
telemetrySchemaVersion | Specifies the version of the Business Central telemetry schema. |
eventId | Unique event ID for different feature telemetry events. |
For error telemetry emitted using LogError
, two extra dimensions are added to custom dimensions.
Dimension | Description or value |
---|---|
alErrorCallStack | The AL stacktrace when the error happened. Typically, this is the stacktrace generated by the GetLastErrorCallStack method. |
alErrorText | A text that describes what the error is about. Typically, this is the error text generated by the GetLastErrorText method. |
This KQL code can help you get started analyzing uptake telemetry
// Uptake telemetry - logged from FeatureTelemetry.LogUptake
traces
| where timestamp > ago(5d) // adjust as needed
| where customDimensions.alCategory == 'FeatureTelemetry'
| where customDimensions.alSubCategory == 'Uptake'
| project timestamp
, aadTenantId = customDimensions.aadTenantId
, environmentName = customDimensions.environmentName
, environmentType = customDimensions.environmentType
, companyName = customDimensions.companyName
, clientType = customDimensions.alClientType
, featureName = customDimensions.alFeatureName
, eventId = customDimensions.eventId
, eventName = customDimensions.alEventName
, category = customDimensions.alCategory
, appName = customDimensions.alCallerAppName // added in 20.0
, appId = customDimensions.alCallerAppId // added in 22.0
, appPublisher = customDimensions.alCallerPublisher // added in 20.0
, appVersion = customDimensions.alCallerAppVersion // added in 20.0
, usertelemetryId = case(
// user telemetry id was introduced in the platform in version 20.0
toint( substring(customDimensions.componentVersion,0,2)) >= 20, user_Id
, 'N/A'
)
This KQL code can help you get started analyzing error telemetry
// Errors - logged from FeatureTelemetry.LogError
traces
| where timestamp > ago(5d)
| where customDimensions.alCategory == 'FeatureTelemetry'
| where customDimensions.alSubCategory == 'Error'
| project timestamp
, aadTenantId = customDimensions.aadTenantId
, environmentName = customDimensions.environmentName
, environmentType = customDimensions.environmentType
, companyName = customDimensions.companyName
, clientType = customDimensions.alClientType
, eventId = customDimensions.eventId
, featureName = customDimensions.alFeatureName
, eventName = customDimensions.alEventName
, category = customDimensions.alCategory
, appName = customDimensions.alCallerAppName // added in 20.0
, appId = customDimensions.alCallerAppId // added in 22.0
, appPublisher = customDimensions.alCallerPublisher // added in 20.0
, appVersion = customDimensions.alCallerAppVersion // added in 20.0
, errorCallStack = customDimensions.alErroCallStack
, errorText = customDimensions.alErrorText
, usertelemetryId = case(
// user telemetry id was introduced in the platform in version 20.0
toint( substring(customDimensions.componentVersion,0,2)) >= 20, user_Id
, 'N/A'
)
This KQL code can help you get started analyzing usage telemetry
// Usage - logged from FeatureTelemetry.LogUsage
traces
| where timestamp > ago(5d)
| where customDimensions.alCategory == 'FeatureTelemetry'
| where customDimensions.alSubCategory == 'Usage'
| project timestamp
, aadTenantId = customDimensions.aadTenantId
, environmentName = customDimensions.environmentName
, environmentType = customDimensions.environmentType
, companyName = customDimensions.companyName
, clientType = customDimensions.alClientType
, eventId = customDimensions.eventId
, featureName = customDimensions.alFeatureName
, eventName = customDimensions.alEventName
, category = customDimensions.alCategory
, appName = customDimensions.alCallerAppName // added in 20.0
, appId = customDimensions.alCallerAppId // added in 22.0
, appPublisher = customDimensions.alCallerPublisher // added in 20.0
, appVersion = customDimensions.alCallerAppVersion // added in 20.0
, usertelemetryId = case(
// user telemetry id was introduced in the platform in version 20.0
toint( substring(customDimensions.componentVersion,0,2)) >= 20, user_Id
, 'N/A'
)
When you use the feature telemetry module in your app, it's important to register exactly one telemetry logger. If you fail to do so, the [!INCLUDEprod_short] server logs an event to your telemetry.
This event is logged if more than one telemetry logger has been registered for publisher.
Dimension | Description or value |
---|---|
message | More than one telemetry logger has been registered for publisher {publisher} |
Dimension | Description or value |
---|---|
aadTenantId | Specifies the Microsoft Entra tenant ID used for Microsoft Entra authentication. For on-premises solutions that do not use Microsoft Entra authentication, this value is common. |
alCallerAppName | The name of the extension that emitted telemetry. |
alCallerAppPublisher | The name of the extension that emitted telemetry. |
alCallerAppVersion | The name of the extension that emitted telemetry. |
environmentName | Specifies the name of the tenant environment. See Managing Environments. This dimension isn't included for [!INCLUDEprod_short] on-premises environments. |
environmentType | Specifies the environment type for the tenant, such as Production, Sandbox, Trial. See Environment Types. |
eventId | AL0000G7J |
This KQL code can help you get started analyzing if an app has registered more than one telemetry logger.
// More than one telemetry logger has been registered for publisher <publisher>
// The owner of the app needs to fix this if they want telemetry from the Feature Telemetry system module
traces
| where timestamp > ago(7d) // change as needed
| where customDimensions has 'AL0000G7J'
| where customDimensions.eventId == 'AL0000G7J'
| project timestamp
, aadTenantId = customDimensions.aadTenantId
, environmentName = customDimensions.environmentName
, environmentType = customDimensions.environmentType
, appId = customDimensions.alCallerAppId
, appPublisher = customDimensions.alCallerPublisher
, appName = customDimensions.alCallerAppName
, appVersion = customDimensions.alCallerAppVersion
This event is logged if no telemetry logger has been registered for publisher.
For more information on how to create a telemetry logger, see section Register the feature telemetry module in an app.
Dimension | Description or value |
---|---|
message | An app from publisher {publisher} is sending telemetry, but there's no registered telemetry logger for this publisher |
Dimension | Description or value |
---|---|
aadTenantId | Specifies the Microsoft Entra tenant ID used for Microsoft Entra authentication. For on-premises, if you aren't using Microsoft Entra authentication, this value is common. |
alCallerAppName | The name of the extension that emitted telemetry. |
alCallerAppPublisher | The name of the extension that emitted telemetry. |
alCallerAppVersion | The name of the extension that emitted telemetry. |
environmentName | Specifies the name of the tenant environment. See Managing Environments. This dimension isn't included for [!INCLUDEprod_short] on-premises environments. |
environmentType | Specifies the environment type for the tenant, such as Production, Sandbox, Trial. See Environment Types. |
eventId | AL0000G7K |
This KQL code can help you get started analyzing if no telemetry logger has been registered for an app.
// An app from the publisher <publisher> sends telemetry, but there is no registered telemetry logger for this publisher.
// The owner of the app must fix this if they want telemetry from the Feature Telemetry system module
traces
| where timestamp > ago(7d) // change as needed
| where customDimensions has 'AL0000G7K'
| where customDimensions.eventId == 'AL0000G7K'
| project timestamp
, aadTenantId = customDimensions.aadTenantId
, environmentName = customDimensions.environmentName
, environmentType = customDimensions.environmentType
, appId = customDimensions.alCallerAppId
, appPublisher = customDimensions.alCallerPublisher
, appName = customDimensions.alCallerAppName
, appVersion = customDimensions.alCallerAppVersion
Error handling
Feature Telemetry sample code
Application Overview
Feature Telemetry System Application Documentation
Feature Telemetry Codeunit Reference Documentation
Feature Management Telemetry
Telemetry Overview
Enable Telemetry in Business Central