You have a Business Central extension that’s running in production, but you have limited visibility into:
You want to add comprehensive telemetry using Application Insights to monitor your extension in production.
Use AI to help you:
Understanding of:
Starting Code (without telemetry):
codeunit 50100 "Sales Order Processor"
{
procedure ProcessOrder(var SalesHeader: Record "Sales Header"): Boolean
var
SalesLine: Record "Sales Line";
begin
if not ValidateOrder(SalesHeader) then
exit(false);
CalculateTotals(SalesHeader);
ApplyDiscounts(SalesHeader);
if not PostOrder(SalesHeader) then
exit(false);
exit(true);
end;
local procedure ValidateOrder(var SalesHeader: Record "Sales Header"): Boolean
var
Customer: Record Customer;
begin
if not Customer.Get(SalesHeader."Sell-to Customer No.") then begin
Error('Customer %1 does not exist.', SalesHeader."Sell-to Customer No.");
end;
if Customer.Blocked <> Customer.Blocked::" " then begin
Error('Customer %1 is blocked.', Customer."No.");
end;
exit(true);
end;
local procedure PostOrder(var SalesHeader: Record "Sales Header"): Boolean
var
SalesPost: Codeunit "Sales-Post";
begin
exit(SalesPost.Run(SalesHeader));
end;
}
Prompt:
I want to add Application Insights telemetry to this Business Central codeunit.
Help me plan what to log:
- What events should be logged?
- What custom dimensions should be included?
- What data classification is appropriate?
- Where should we log errors vs. information?
[Paste the code above]
Expected AI Response: The AI should suggest logging:
Events to Log:
Custom Dimensions:
Data Classification:
Prompt:
Add Application Insights telemetry to the ProcessOrder procedure.
Include:
- Start event when processing begins
- Success event when complete
- Error event on failure
- Custom dimensions: OrderNo, CustomerNo, TotalAmount, LineCount, Duration
- Use appropriate data classification
- Use the Telemetry codeunit from System Application
Update this code:
[Paste ProcessOrder procedure]
Expected Result:
procedure ProcessOrder(var SalesHeader: Record "Sales Header"): Boolean
var
Telemetry: Codeunit Telemetry;
CustomDimensions: Dictionary of [Text, Text];
StartTime: DateTime;
Duration: Duration;
LineCount: Integer;
begin
StartTime := CurrentDateTime;
// Log start of processing
AddOrderDimensions(CustomDimensions, SalesHeader);
Telemetry.LogMessage('SALESORD-001', 'Order processing started',
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
if not ValidateOrder(SalesHeader) then begin
LogProcessingError(SalesHeader, 'Validation failed');
exit(false);
end;
CalculateTotals(SalesHeader);
ApplyDiscounts(SalesHeader);
if not PostOrder(SalesHeader) then begin
LogProcessingError(SalesHeader, 'Posting failed');
exit(false);
end;
// Log successful completion
Duration := CurrentDateTime - StartTime;
Clear(CustomDimensions);
AddOrderDimensions(CustomDimensions, SalesHeader);
CustomDimensions.Add('Duration', Format(Duration));
CustomDimensions.Add('LineCount', Format(CountLines(SalesHeader)));
Telemetry.LogMessage('SALESORD-002', 'Order processing completed successfully',
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
exit(true);
end;
local procedure AddOrderDimensions(var CustomDimensions: Dictionary of [Text, Text]; SalesHeader: Record "Sales Header")
begin
CustomDimensions.Add('OrderNo', SalesHeader."No.");
CustomDimensions.Add('CustomerNo', SalesHeader."Sell-to Customer No.");
CustomDimensions.Add('OrderType', Format(SalesHeader."Document Type"));
end;
local procedure LogProcessingError(SalesHeader: Record "Sales Header"; ErrorMessage: Text)
var
Telemetry: Codeunit Telemetry;
CustomDimensions: Dictionary of [Text, Text];
begin
AddOrderDimensions(CustomDimensions, SalesHeader);
CustomDimensions.Add('ErrorMessage', ErrorMessage);
CustomDimensions.Add('ErrorCallStack', GetLastErrorCallStack());
Telemetry.LogMessage('SALESORD-E001', StrSubstNo('Order processing failed: %1', ErrorMessage),
Verbosity::Error, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;
local procedure CountLines(SalesHeader: Record "Sales Header"): Integer
var
SalesLine: Record "Sales Line";
begin
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
exit(SalesLine.Count);
end;
Prompt:
Update the ValidateOrder procedure to log validation failures with telemetry.
For each validation check that fails:
- Log a warning message
- Include relevant details (customer number, block reason, etc.)
- Use appropriate custom dimensions
- Use proper data classification
[Paste ValidateOrder procedure]
Expected Result:
local procedure ValidateOrder(var SalesHeader: Record "Sales Header"): Boolean
var
Customer: Record Customer;
Telemetry: Codeunit Telemetry;
CustomDimensions: Dictionary of [Text, Text];
begin
if not Customer.Get(SalesHeader."Sell-to Customer No.") then begin
AddValidationDimensions(CustomDimensions, SalesHeader, 'Customer not found');
Telemetry.LogMessage('SALESORD-W001', 'Order validation failed: Customer not found',
Verbosity::Warning, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
Error('Customer %1 does not exist.', SalesHeader."Sell-to Customer No.");
end;
if Customer.Blocked <> Customer.Blocked::" " then begin
Clear(CustomDimensions);
AddValidationDimensions(CustomDimensions, SalesHeader, 'Customer blocked');
CustomDimensions.Add('BlockedReason', Format(Customer.Blocked));
Telemetry.LogMessage('SALESORD-W002', 'Order validation failed: Customer blocked',
Verbosity::Warning, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
Error('Customer %1 is blocked.', Customer."No.");
end;
exit(true);
end;
local procedure AddValidationDimensions(var CustomDimensions: Dictionary of [Text, Text]; SalesHeader: Record "Sales Header"; Reason: Text)
begin
CustomDimensions.Add('OrderNo', SalesHeader."No.");
CustomDimensions.Add('CustomerNo', SalesHeader."Sell-to Customer No.");
CustomDimensions.Add('ValidationFailure', Reason);
end;
Prompt:
Add performance telemetry to track how long different operations take.
Track duration for:
- Overall processing
- Validation
- Calculation
- Posting
Log if any operation takes longer than expected thresholds:
- Validation > 1 second
- Calculation > 2 seconds
- Posting > 5 seconds
[Include the codeunit]
Expected Result:
local procedure ValidateOrder(var SalesHeader: Record "Sales Header"): Boolean
var
Customer: Record Customer;
Telemetry: Codeunit Telemetry;
CustomDimensions: Dictionary of [Text, Text];
StartTime: DateTime;
Duration: Duration;
begin
StartTime := CurrentDateTime;
// Validation logic here...
Duration := CurrentDateTime - StartTime;
// Log if validation took too long
if Duration > 1000 then begin
AddOrderDimensions(CustomDimensions, SalesHeader);
CustomDimensions.Add('Duration', Format(Duration));
CustomDimensions.Add('Operation', 'Validation');
Telemetry.LogMessage('SALESORD-P001', 'Slow validation detected',
Verbosity::Warning, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;
exit(true);
end;
Prompt:
Add telemetry to track feature usage for business analytics.
Track:
- How often each discount type is applied
- Which payment methods are used
- Popular shipping methods
- Order size distribution
Create a FeatureUsageTelemetry codeunit for this purpose.
Expected Result:
codeunit 50101 "Feature Usage Telemetry"
{
var
Telemetry: Codeunit Telemetry;
procedure LogDiscountApplied(DiscountType: Text; DiscountPct: Decimal; OrderNo: Code[20])
var
CustomDimensions: Dictionary of [Text, Text];
begin
CustomDimensions.Add('FeatureArea', 'Discounts');
CustomDimensions.Add('DiscountType', DiscountType);
CustomDimensions.Add('DiscountPercentage', Format(DiscountPct));
CustomDimensions.Add('OrderNo', OrderNo);
Telemetry.LogMessage('FEATURE-DISCOUNT', 'Discount applied',
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;
procedure LogPaymentMethod(PaymentMethodCode: Code[10]; Amount: Decimal)
var
CustomDimensions: Dictionary of [Text, Text];
begin
CustomDimensions.Add('FeatureArea', 'Payment');
CustomDimensions.Add('PaymentMethod', PaymentMethodCode);
CustomDimensions.Add('AmountRange', GetAmountRange(Amount));
Telemetry.LogMessage('FEATURE-PAYMENT', 'Payment method used',
Verbosity::Normal, DataClassification::SystemMetadata,
TelemetryScope::ExtensionPublisher, CustomDimensions);
end;
local procedure GetAmountRange(Amount: Decimal): Text
begin
case true of
Amount < 100:
exit('0-100');
Amount < 1000:
exit('100-1000');
Amount < 10000:
exit('1000-10000');
else
exit('10000+');
end;
end;
}
Use Consistent Event IDs
SALESORD-001: Processing started
SALESORD-002: Processing completed
SALESORD-E001: Processing error
SALESORD-W001: Validation warning
SALESORD-P001: Performance warning
Use Meaningful Messages
// Good
Telemetry.LogMessage('SALESORD-001', 'Sales order processing started for large order', ...);
// Bad
Telemetry.LogMessage('001', 'Started', ...);
Include Helpful Custom Dimensions
CustomDimensions.Add('OrderNo', OrderNo);
CustomDimensions.Add('CustomerNo', CustomerNo);
CustomDimensions.Add('LineCount', Format(LineCount));
CustomDimensions.Add('TotalAmount', Format(TotalAmount));
CustomDimensions.Add('ProcessingDuration', Format(Duration));
Use Appropriate Data Classification
// Customer data
DataClassification::CustomerContent
// System metrics
DataClassification::SystemMetadata
// Organizational data
DataClassification::OrganizationIdentifiableInformation
Don’t Over-Log
// Too much logging
Telemetry.LogMessage('001', 'Line 1', ...);
Telemetry.LogMessage('002', 'Line 2', ...);
// Don't log every single step
Don’t Log Sensitive Data
// Bad - logging password
CustomDimensions.Add('Password', Password);
// Bad - logging full credit card
CustomDimensions.Add('CreditCard', CreditCardNo);
// Bad - logging personal data unnecessarily
CustomDimensions.Add('SSN', SSN);
Don’t Use Generic Messages
// Bad
Telemetry.LogMessage('001', 'Error', ...);
// Good
Telemetry.LogMessage('SALESORD-E001', 'Failed to post sales order due to credit limit exceeded', ...);
Prompt:
Create a telemetry wrapper codeunit that simplifies logging for my extension.
Include helper methods for:
- LogStart/LogEnd (with automatic duration calculation)
- LogError (with automatic call stack)
- LogFeatureUsage
- LogPerformanceWarning
Prefix all event IDs with my extension prefix "SALESEXT-"
Prompt:
Create a codeunit with constants for all my telemetry event IDs and messages.
This will ensure consistency and make it easier to update messages.
Prompt:
Create a system that automatically adds common context to all telemetry:
- User ID (anonymized)
- Company name (anonymized)
- Extension version
- BC version
- Environment type (Production/Sandbox)
Make this context added automatically to all telemetry calls.
Prompt:
Create KQL (Kusto Query Language) queries for Application Insights to analyze:
1. Most common errors in the last 24 hours
2. Average processing duration by order size
3. Feature usage statistics
4. Performance trends over time
5. Error rate percentage
Base the queries on the telemetry we added to the Sales Order Processor codeunit.
Expected Result:
// Most common errors in last 24 hours
traces
| where timestamp > ago(24h)
| where message startswith "SALESORD-E"
| summarize ErrorCount = count() by message, customDimensions.ErrorMessage
| order by ErrorCount desc
| take 10
// Average processing duration by line count
traces
| where message == "SALESORD-002"
| extend LineCount = toint(customDimensions.LineCount)
| extend Duration = toint(customDimensions.Duration)
| summarize AvgDuration = avg(Duration) by LineCountBucket = bin(LineCount, 5)
| order by LineCountBucket asc
// Feature usage - discount types
traces
| where message == "FEATURE-DISCOUNT"
| summarize UsageCount = count() by DiscountType = tostring(customDimensions.DiscountType)
| order by UsageCount desc
// Performance warnings
traces
| where message startswith "SALESORD-P"
| summarize WarningCount = count() by Operation = tostring(customDimensions.Operation), bin(timestamp, 1h)
| render timechart
// Error rate percentage
let totalOrders = toscalar(
traces
| where message == "SALESORD-001"
| where timestamp > ago(24h)
| count
);
let failedOrders = toscalar(
traces
| where message startswith "SALESORD-E"
| where timestamp > ago(24h)
| count
);
print ErrorRate = (todouble(failedOrders) / todouble(totalOrders)) * 100
Add comprehensive telemetry to this code:
codeunit 50200 "Inventory Adjustment Handler"
{
procedure AdjustInventory(ItemNo: Code[20]; Quantity: Decimal; ReasonCode: Code[10])
var
Item: Record Item;
ItemJnlLine: Record "Item Journal Line";
begin
Item.Get(ItemNo);
ItemJnlLine.Init();
ItemJnlLine."Item No." := ItemNo;
ItemJnlLine.Quantity := Quantity;
ItemJnlLine."Reason Code" := ReasonCode;
ItemJnlLine.Insert(true);
CODEUNIT.Run(CODEUNIT::"Item Jnl.-Post Line", ItemJnlLine);
end;
}
Your Tasks:
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.