Skip to main content

BC Telemetry Generator

Instruments Business Central AL codeunits with Application Insights telemetry. Analyzes a target codeunit, creates or extends a dedicated telemetry helper codeunit, and adds Telemetry.LogMessage calls following Microsoft best practices.

Reference: alguidelines.dev — Adding Telemetry

Prerequisites​

  • Target codeunit to instrument (user provides the codeunit)
  • Available object ID for telemetry helper codeunit
  • Extension prefix established (e.g., BCS)
  • Application Insights configured in extension (or environment)

Workflow​

Step 1: Read the Target Codeunit​

Ask the user which codeunit to instrument. Read the file and identify:

  • Public procedures — entry points needing start/end tracking
  • Validation procedures — needing warning-level telemetry on failure
  • Error-prone operations — posting, external calls, database writes
  • Performance-sensitive paths — loops, bulk operations, external HTTP calls
  • Feature decision points — discount type selection, payment method, routing logic

Step 2: Plan Telemetry Strategy​

For each identified procedure, determine:

CategoryVerbosityWhen to Log
Lifecycle (start/end)NormalEntry points, key milestones
ErrorErrorCatch blocks, posting failures, validation errors
WarningWarningValidation failures, degraded paths
PerformanceWarningOperations exceeding threshold
Feature usageNormalBusiness decisions, option selections

Event ID scheme: [PREFIX]-[AREA][NNN] for info, [PREFIX]-[AREA]E[NNN] for errors, [PREFIX]-[AREA]W[NNN] for warnings, [PREFIX]-[AREA]P[NNN] for performance.

Example: BCS-SALES001, BCS-SALESE001, BCS-SALESW001, BCS-SALESP001

Step 3: Create or Extend Telemetry Helper Codeunit​

Check if a telemetry helper already exists in the workspace. If so, add new procedures to it. If not, create one.

File location: Same feature folder as the target codeunit, or Codeunit/ folder.

Naming: [Prefix] [Feature] Telemetry (e.g., BCS Sales Telemetry)

Telemetry Helper Structure​

codeunit [ID] "[Prefix] [Feature] Telemetry"
{
Access = Internal;
SingleInstance = true;

var
Telemetry: Codeunit Telemetry;

procedure LogOperationStarted(EventId: Text; Message: Text; CustomDimensions: Dictionary of [Text, Text])
begin
Telemetry.LogMessage(
EventId, Message,
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;

procedure LogOperationCompleted(EventId: Text; Message: Text; StartTime: DateTime; CustomDimensions: Dictionary of [Text, Text])
var
DurationMs: Duration;
begin
DurationMs := CurrentDateTime - StartTime;
CustomDimensions.Set('DurationMs', Format(DurationMs));
Telemetry.LogMessage(
EventId, Message,
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;

procedure LogError(EventId: Text; Message: Text; CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Set('ErrorMessage', GetLastErrorText());
CustomDimensions.Set('ErrorCallStack', GetLastErrorCallStack());
Telemetry.LogMessage(
EventId, Message,
Verbosity::Error, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;

procedure LogWarning(EventId: Text; Message: Text; CustomDimensions: Dictionary of [Text, Text])
begin
Telemetry.LogMessage(
EventId, Message,
Verbosity::Warning, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;

procedure LogFeatureUsage(EventId: Text; FeatureArea: Text; FeatureAction: Text; CustomDimensions: Dictionary of [Text, Text])
begin
CustomDimensions.Set('FeatureArea', FeatureArea);
CustomDimensions.Set('FeatureAction', FeatureAction);
Telemetry.LogMessage(
EventId, FeatureArea + ' - ' + FeatureAction,
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;

procedure LogPerformanceWarning(EventId: Text; OperationName: Text; DurationMs: Duration; ThresholdMs: Integer; CustomDimensions: Dictionary of [Text, Text])
begin
if DurationMs <= ThresholdMs then
exit;

CustomDimensions.Set('Operation', OperationName);
CustomDimensions.Set('DurationMs', Format(DurationMs));
CustomDimensions.Set('ThresholdMs', Format(ThresholdMs));
Telemetry.LogMessage(
EventId,
StrSubstNo('Slow operation detected: %1 (%2 ms, threshold %3 ms)', OperationName, DurationMs, ThresholdMs),
Verbosity::Warning, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;
}

Step 4: Add Dimension Builder Procedures​

For each entity involved, create a dimension builder that collects standard fields:

local procedure AddOrderDimensions(var Dims: Dictionary of [Text, Text]; SalesHeader: Record "Sales Header")
begin
Dims.Add('OrderNo', SalesHeader."No.");
Dims.Add('CustomerNo', SalesHeader."Sell-to Customer No.");
Dims.Add('DocumentType', Format(SalesHeader."Document Type"));
end;

Rules for custom dimensions:

  • Use CustomerContent classification label for business data (order no, customer no, amounts)
  • Use SystemMetadata for technical data (duration, counts, operation names)
  • Use EndUserIdentifiableInformation for user references — anonymize when possible
  • Never log passwords, API keys, credit card numbers, or PII
  • Keep dimension count under 15 per event for readability

Step 5: Instrument the Target Codeunit​

Add telemetry calls to the target codeunit using the helper:

Lifecycle Events (Start/End)​

procedure ProcessOrder(var SalesHeader: Record "Sales Header"): Boolean
var
BCStelemetry: Codeunit "BCS Sales Telemetry";
CustomDimensions: Dictionary of [Text, Text];
StartTime: DateTime;
begin
StartTime := CurrentDateTime;
AddOrderDimensions(CustomDimensions, SalesHeader);
BCStelemetry.LogOperationStarted('BCS-SALES001', 'Order processing started', CustomDimensions);

// ... existing logic ...

Clear(CustomDimensions);
AddOrderDimensions(CustomDimensions, SalesHeader);
BCStelemetry.LogOperationCompleted('BCS-SALES002', 'Order processing completed', StartTime, CustomDimensions);
exit(true);
end;

Error Tracking​

if not PostOrder(SalesHeader) then begin
Clear(CustomDimensions);
AddOrderDimensions(CustomDimensions, SalesHeader);
BCStelemetry.LogError('BCS-SALESE001', 'Order posting failed', CustomDimensions);
exit(false);
end;

Validation Warnings​

if Customer.Blocked <> Customer.Blocked::" " then begin
Clear(CustomDimensions);
CustomDimensions.Add('CustomerNo', SalesHeader."Sell-to Customer No.");
CustomDimensions.Add('BlockedReason', Format(Customer.Blocked));
BCStelemetry.LogWarning('BCS-SALESW001', 'Order validation failed: Customer blocked', CustomDimensions);
Error('Customer %1 is blocked.', Customer."No.");
end;

Performance Monitoring​

StartTime := CurrentDateTime;
ValidateOrder(SalesHeader);
Duration := CurrentDateTime - StartTime;

BCStelemetry.LogPerformanceWarning(
'BCS-SALESP001', 'ValidateOrder', Duration, 1000, CustomDimensions);

Feature Usage​

BCStelemetry.LogFeatureUsage(
'BCS-FEAT001', 'Discounts', 'Line Discount Applied', CustomDimensions);

Step 6: Build and Validate​

  • Run al_build to verify compilation
  • Check @problems for errors
  • Verify no sensitive data is logged

Event ID Convention​

PatternMeaningExample
PREFIX-AREA###InformationalBCS-SALES001
PREFIX-AREAE###ErrorBCS-SALESE001
PREFIX-AREAW###WarningBCS-SALESW001
PREFIX-AREAP###PerformanceBCS-SALESP001
PREFIX-FEAT###Feature usageBCS-FEAT001
  • Keep the AREA tag short (4-8 chars)
  • Number sequentially within a category
  • Maintain an event ID registry comment at the top of the telemetry codeunit

DataClassification Rules​

Data TypeClassificationExamples
Technical metricsSystemMetadataDuration, counts, operation name
Business identifiersCustomerContentOrder No., Customer No., amounts
User referencesEndUserIdentifiableInformationUser ID (anonymize!)
Sensitive dataNEVER LOGPasswords, API keys, credit cards

Best Practices​

  1. Don't over-log — log entry/exit of key operations, not every line
  2. Use consistent event IDs — prefix + area + sequential number
  3. Include helpful dimensions — enough to diagnose issues, not everything
  4. Clear dimensions before reuse — call Clear(CustomDimensions) between events
  5. Log at appropriate verbosity — Normal for info, Warning for degraded paths, Error for failures
  6. Always use TelemetryScope::ExtensionPublisher — routes to your Application Insights
  7. Keep dimension values short — Application Insights truncates at ~8 KB per event
  8. Never log PII — anonymize user IDs, never log passwords or tokens
  9. Add duration to completion events — essential for performance analysis
  10. Leave telemetry helper body empty for feature usage — just add dimensions and call helper

Advanced Patterns​

External References​