Add lifecycle error and privacy knowledge

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 95c06ad8-377d-4faa-8d07-06300b1c81ec
This commit is contained in:
Jesper Schulz-Wedde 2026-07-14 12:02:49 +02:00
parent 9214f73819
commit 0c2a0ceb82
25 changed files with 457 additions and 37 deletions

View file

@ -0,0 +1,13 @@
codeunit 50308 "ErrorInfo Privacy Bad"
{
procedure RaiseSynchronizationError(Customer: Record Customer; ResponseBody: Text)
var
FailureInfo: ErrorInfo;
begin
FailureInfo.Message := StrSubstNo('Synchronization failed for %1.', Customer."E-Mail");
FailureInfo.DataClassification := DataClassification::SystemMetadata;
FailureInfo.ErrorType := ErrorType::Internal;
FailureInfo.DetailedMessage := ResponseBody;
Error(FailureInfo);
end;
}

View file

@ -0,0 +1,17 @@
codeunit 50307 "ErrorInfo Privacy Good"
{
procedure RaiseSynchronizationError()
var
FailureInfo: ErrorInfo;
begin
FailureInfo.Message := SynchronizationFailedErr;
FailureInfo.DataClassification := DataClassification::SystemMetadata;
FailureInfo.ErrorType := ErrorType::Client;
FailureInfo.DetailedMessage := RetryDiagnosticsTxt;
Error(FailureInfo);
end;
var
RetryDiagnosticsTxt: Label 'The remote service rejected the request. Review the integration telemetry event.';
SynchronizationFailedErr: Label 'The synchronization could not be completed.';
}

View file

@ -0,0 +1,26 @@
---
bc-version: [19..]
domain: privacy
keywords: [errorinfo, message, dataclassification, errortype, detailedmessage, copy-details, telemetry]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Review each ErrorInfo text surface by its actual exposure
## Description
`ErrorInfo.Message` is sent to telemetry; with `ErrorType::Client` it is also the primary client message, while `ErrorType::Internal` replaces it in the client with a generic message but still sends the specified text to telemetry. `DataClassification` classifies the content in `Message`; it does not make incorrectly classified personal data safe. `DetailedMessage`, available from BC 19, is omitted from the primary message but is included in the error dialog's **Copy details** content.
## Best Practice
Keep `Message` stable and classify its actual content. Choose `ErrorType` for client usability, not as a telemetry privacy boundary. Put only support-safe technical context in `DetailedMessage`, because a user can copy it from the dialog.
See sample: `errorinfo-telemetry-classification-and-errortype.good.al`.
## Anti Pattern
Marking a dynamic customer-bearing `Message` as `SystemMetadata`, assuming `ErrorType::Internal` keeps it out of telemetry, or placing secrets and personal data in `DetailedMessage` because it is not the primary dialog text.
See sample: `errorinfo-telemetry-classification-and-errortype.bad.al`.

View file

@ -0,0 +1,25 @@
codeunit 50310 "LogError Privacy Bad"
{
procedure SendInvoice()
var
FeatureTelemetry: Codeunit "Feature Telemetry";
CustomDimensions: Dictionary of [Text, Text];
ErrorCallStack: Text;
ErrorText: Text;
begin
if TrySendInvoice() then
exit;
ErrorText := GetLastErrorText();
ErrorCallStack := GetLastErrorCallStack();
CustomDimensions.Add('Operation', 'SendInvoice');
FeatureTelemetry.LogError('0000FT2', 'Invoice exchange', 'Sending invoice',
ErrorText, ErrorCallStack, CustomDimensions);
end;
[TryFunction]
local procedure TrySendInvoice()
begin
Error('Invoice %1 for %2 could not be sent.', 'INV-1001', 'user@example.com');
end;
}

View file

@ -0,0 +1,28 @@
codeunit 50309 "LogError Privacy Good"
{
procedure SendInvoice()
var
FeatureTelemetry: Codeunit "Feature Telemetry";
CustomDimensions: Dictionary of [Text, Text];
ErrorCallStack: Text;
ErrorText: Text;
begin
if TrySendInvoice() then
exit;
ErrorText := GetLastErrorText(true);
ErrorCallStack := GetLastErrorCallStack();
CustomDimensions.Add('Operation', 'SendInvoice');
FeatureTelemetry.LogError('0000FT1', 'Invoice exchange', 'Sending invoice',
ErrorText, ErrorCallStack, CustomDimensions);
end;
[TryFunction]
local procedure TrySendInvoice()
begin
Error(SendFailedErr);
end;
var
SendFailedErr: Label 'The invoice could not be sent.';
}

View file

@ -0,0 +1,26 @@
---
bc-version: [18..]
domain: privacy
keywords: [featuretelemetry, logerror, errortext, errorcallstack, alerrortext, alerrorcallstack, customdimensions]
technologies: [al]
countries: [w1]
application-area: [all]
---
# FeatureTelemetry.LogError emits more than caller custom dimensions
## Description
`FeatureTelemetry.LogError` emits its `ErrorText` as the telemetry message and adds it as `alErrorText`. The overloads with `ErrorCallStack` also add `alErrorCallStack`. These dimensions are produced in addition to the caller-supplied `CustomDimensions` dictionary, and the Feature Telemetry implementation sends the event as `SystemMetadata`.
## Best Practice
Review the dedicated error arguments as telemetry payload. Capture `GetLastErrorText(true)` when scrubbed platform error text is sufficient, and pass `GetLastErrorCallStack()` only as a call stack. Keep custom dimensions non-personal too.
See sample: `featuretelemetry-logerror-implicit-errortext.good.al`.
## Anti Pattern
Approving a `LogError` call because its explicit dictionary contains only safe values while it passes unsanitized `GetLastErrorText()` or arbitrary context through `ErrorText` or `ErrorCallStack`. Those arguments become telemetry dimensions outside the dictionary.
See sample: `featuretelemetry-logerror-implicit-errortext.bad.al`.