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