La question à laquelle personne ne sait répondre à neuf heures du matin
Un commercial augmente la limite de crédit d'un client dans Dynamics 365. Une demi-heure plus tard, l'ERP affiche toujours l'ancienne valeur. L'équipe Dynamics affirme que l'enregistrement a bien été sauvegardé, l'équipe Azure que la fonction est au vert, et l'équipe ERP que rien n'est arrivé. Chacun a raison pour sa propre partie, et personne ne peut prouver ce qu'il est advenu de cette modification.
Dans cet article, je décris comment je mets en place une supervision de bout en bout pour les intégrations Dataverse construites sur Azure Integration Services : extraire la télémétrie de Dataverse, instrumenter Service Bus, Azure Functions et Logic Apps, suivre une transaction métier à travers chaque étape grâce à un identifiant de corrélation, et créer des alertes sur ce qui compte vraiment. La seconde partie traite des identités managées, car ces mêmes intégrations regorgent généralement de chaînes de connexion et de secrets clients que personne ne veut renouveler. Je m'en tiens à ce que Microsoft documente aujourd'hui et je signale explicitement ce qui est en préversion ou limité. Pour le volet Dataverse de l'architecture, commencez par la documentation sur l'intégration Azure avec Microsoft Dataverse.
Pourquoi : une modification, cinq étapes, trois équipes
Une intégration événementielle typique avec Dynamics 365 comporte plus de pièces mobiles que le schéma au tableau blanc ne le laisse croire :
- Dataverse : un utilisateur ou un processus modifie une ligne, et les plugins s'exécutent dans le pipeline d'événements.
- Plugin ou point de terminaison de service : une étape enregistrée publie le contexte d'exécution vers Azure Service Bus, de manière synchrone ou via un travail système asynchrone.
- Rubrique Service Bus : le message attend dans un abonnement jusqu'à ce qu'un consommateur le récupère, qu'il soit retenté ou qu'il finisse dans la file de lettres mortes.
- Azure Function ou Logic App : le consommateur transforme, enrichit et valide les données, en rappelant souvent Dataverse pour obtenir du contexte supplémentaire.
- Système cible : un ERP, une plateforme de données ou l'API d'un partenaire accepte ou rejette la mise à jour.
Chaque étape a ses propres journaux, sa propre durée de conservation et souvent son propre responsable. Sans télémétrie partagée, une erreur à l'étape 4 ressemble pour le métier à un problème Dynamics, puisque c'est là que la modification a été faite. J'ai vu des équipes débattre pendant des jours pour savoir à qui imputer une mise à jour manquante, alors qu'une seule requête sur les bonnes tables aurait tranché la question en quelques minutes.
L'objectif
Pour toute modification métier, la personne de garde doit pouvoir répondre en quelques minutes à trois questions : la modification a-t-elle quitté Dataverse, où se trouve-t-elle maintenant et, en cas d'échec, pourquoi ? Tout ce qui suit sert ces trois questions.
La conception de la télémétrie en quatre décisions
Avant de configurer quoi que ce soit, je fixe quatre principes avec les personnes qui assureront le support de l'intégration :
- Un espace de travail Log Analytics par niveau d'environnement. Application Insights basé sur un espace de travail stocke ses données dans Log Analytics : la télémétrie Application Insights, les journaux de ressources Service Bus et les journaux Logic Apps peuvent ainsi être interrogés au même endroit.
- Une ressource Application Insights par environnement Dataverse pour l'export de la plateforme. Microsoft indique que les rapports standard ne fonctionnent pas correctement lorsqu'une ressource Application Insights contient des données de plusieurs environnements, et que l'intégration exige l'authentification locale sur cette ressource.
- Une ressource Application Insights distincte pour les composants Azure. Vous pouvez ainsi passer le volet Azure à une ingestion exclusivement via Microsoft Entra sans casser l'export Dataverse, tandis que les deux ressources écrivent toujours dans le même espace de travail.
- Un identifiant de corrélation métier, nommé et formaté de la même façon partout. Les identifiants d'opération de la plateforme changent à chaque étape qui n'est pas reliée par le contexte de trace W3C. Un identifiant de corrélation que vous transportez vous-même les traverse toutes.
La quatrième décision est la plus importante. D'après ce que j'ai pu trouver dans la documentation, Dataverse n'ajoute pas de contexte de trace W3C aux messages Service Bus envoyés par un point de terminaison de service : les identifiants d'opération de la plateforme ne relient donc pas d'eux-mêmes Dataverse et Azure. L'identifiant de corrélation, si.
Côté Dataverse : exporter la télémétrie de la plateforme
Le centre d'administration Power Platform peut exporter la télémétrie vers Application Insights via Gérer > Exportation des données > App Insights. Vous choisissez le type de données (diagnostics et performances Dataverse, Power Automate ou Copilot Studio), des filtres, l'environnement source et la ressource Application Insights cible. Microsoft décrit la configuration dans Exporter des données vers Application Insights.
Prérequis à vérifier d'abord
- Environnements gérés : la fonctionnalité n'est activée et prise en charge que pour les environnements gérés. Rendre un environnement géré a des conséquences sur les licences : toute utilisation active exige alors des licences autonomes éligibles (plusieurs licences Dynamics 365 Enterprise le sont), et Microsoft a annoncé qu'à partir de février 2027, les utilisateurs sans licence appropriée ne pourront plus ouvrir d'applications dans les environnements gérés. Vérifiez ce point avant de promettre de la télémétrie. Voir Exigences de licence pour les environnements gérés.
- Rôles d'administration des deux côtés : la personne qui crée l'export doit disposer du rôle d'administrateur Power Platform ou d'administrateur Dynamics 365 au niveau du locataire, du rôle Administrateur système ou administrateur d'environnement dans l'environnement, et du rôle Contributeur ou Propriétaire sur la ressource Application Insights. Microsoft cite des autorisations insuffisantes comme cause fréquente de télémétrie manquante.
- L'authentification locale reste active : l'intégration exige l'authentification locale sur la ressource Application Insights. Ne la désactivez donc pas sur la ressource qui reçoit l'export Dataverse.
- Prévoir un délai : les données commencent à arriver dans les 24 heures suivant la configuration, et le niveau de service documenté pour la livraison de ces flux de télémétrie est de 24 heures. Considérez-le comme une source de diagnostic, pas comme une source d'alertes en temps réel.
- Préversion : l'export de la télémétrie de Power Automate et de Dynamics 365 Customer Service est en préversion publique et n'est pas disponible dans GCC, GCC High ni 21Vianet.
Pour Dataverse, l'export contient les appels entrants à la Web API et au service d'organisation dans la table requests, les exécutions de plugins (type Plugin) et les appels SDK dans la table dependencies, ainsi que les échecs de plugins et d'opérations SDK dans la table exceptions, avec des dimensions personnalisées comme le nom de l'étape, la profondeur et le nom de l'entité. Tout appel sortant effectué par un plugin est également journalisé comme dépendance. Les détails figurent dans Événements de télémétrie pour Microsoft Dataverse.
Ce que l'export ne vous donne pas
Il vous indique qu'un plugin s'est exécuté, combien de temps cela a pris et s'il a levé une exception. Il ne vous dit pas ce que votre code a décidé ni quel enregistrement il a transmis à Service Bus. Pour cela, le plugin doit écrire sa propre télémétrie.
Journalisation des plugins : ILogger et ITracingService côte à côte
Dataverse offre aux plugins deux canaux de journalisation, qui aboutissent à des endroits différents. Microsoft recommande d'utiliser les deux, comme expliqué dans Écrire de la télémétrie dans votre ressource Application Insights avec ILogger.
| Critère | ILogger (Microsoft.Xrm.Sdk.PluginTelemetry) | ITracingService |
|---|---|---|
| Destination | Votre propre ressource Application Insights (traces et exceptions), écrite directement et jamais envoyée à Microsoft | La table Plugin Trace Log, la boîte de dialogue d'erreur pour les étapes synchrones ou le travail système pour les étapes asynchrones |
| Prérequis | L'export Application Insights depuis le centre d'administration, et donc un environnement géré ; sinon rien n'est écrit | La journalisation des traces de plugin réglée sur Exception ou Tout dans l'environnement |
| Conservation | La durée de conservation de votre Application Insights (90 jours par défaut) | 24 heures ; une suppression en bloc quotidienne efface les enregistrements plus anciens |
| Volume | Une chaîne par appel de journalisation | 10 Ko de texte par exécution ; au-delà, les lignes les plus anciennes sont supprimées |
| Qui peut le lire | Toute personne disposant d'un accès en lecture à la ressource Application Insights ou à l'espace de travail | Le bloc de messages et les colonnes de configuration (sécurisée) ne sont lisibles que par les administrateurs système |
En pratique, je garde ITracingService pour le débogage et pour les détails d'erreur que voient les utilisateurs et le support, et j'ajoute ILogger pour la télémétrie que je veux encore interroger des semaines plus tard. Utilisez l'espace de noms Microsoft.Xrm.Sdk.PluginTelemetry : si vous importez Microsoft.Extensions.Logging à la place, l'instance ILogger est nulle. ILogger n'est pas non plus pris en charge dans une session de profilage ou de débogage de l'outil Plugin Registration.
Un plugin qui ajoute l'identifiant de corrélation et publie vers Service Bus
Je préfère un petit plugin Azure-aware personnalisé à l'étape standard du point de terminaison de service. Il me permet d'écrire de la télémétrie au moment du transfert et d'ajouter l'identifiant de corrélation aux variables partagées du contexte d'exécution, que Microsoft documente comme modifiables avant la publication. Le point de terminaison de service lui-même est enregistré avec JSON comme format de message.
using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.PluginTelemetry; // not Microsoft.Extensions.Logging
namespace Crx.Integration.Plugins
{
// Registered asynchronously on Update of account (PostOperation).
// Unsecure configuration = ID of the Service Bus service endpoint (message format: JSON).
public class AccountPostUpdateErpSyncPlugin : IPlugin
{
private readonly Guid _serviceEndpointId;
public AccountPostUpdateErpSyncPlugin(string unsecureConfig)
{
if (!Guid.TryParse(unsecureConfig, out _serviceEndpointId))
{
throw new InvalidPluginExecutionException("Service endpoint ID missing from the step configuration.");
}
}
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var logger = (ILogger)serviceProvider.GetService(typeof(ILogger));
var notificationService = (IServiceEndpointNotificationService)serviceProvider.GetService(typeof(IServiceEndpointNotificationService));
// One ID for the whole journey. Dataverse's own CorrelationId is a sensible default.
var correlationId = context.CorrelationId.ToString();
context.SharedVariables["crx_correlationid"] = correlationId;
try
{
logger?.LogInformation("ERP sync: posting {0} {1}. CorrelationId {2}",
context.PrimaryEntityName, context.PrimaryEntityId, correlationId);
tracingService.Trace($"ERP sync: posting {context.PrimaryEntityName} {context.PrimaryEntityId}. CorrelationId {correlationId}");
// Keep this plug-in to "stamp, log, post": a failed async post re-runs the whole plug-in
notificationService.Execute(new EntityReference("serviceendpoint", _serviceEndpointId), context);
}
catch (Exception ex)
{
logger?.LogError(ex, "ERP sync: post to Service Bus failed. CorrelationId {0}", correlationId);
tracingService.Trace($"Error: {ex.Message}");
throw new InvalidPluginExecutionException($"An error occurred: {ex.Message}");
}
}
}
}Deux détails documentés guident cette conception. Le contexte d'exécution dans le corps du message contient le CorrelationId et, pour les étapes asynchrones, l'OperationId du travail système, ce qui aide à détecter les doublons. Et lorsqu'une publication asynchrone échoue, le travail système réessaie en exécutant à nouveau l'intégralité du plugin : celui-ci doit donc se limiter à modifier le contexte, journaliser et publier. Gardez aussi à l'esprit qu'une publication synchrone ne peut pas être annulée si la transaction est ensuite restaurée. Voir Écrire un plugin Azure-aware personnalisé.
Côté Azure : Functions, Logic Apps et Service Bus
Côté Azure, l'objectif est que chaque composant écrive dans le même espace de travail, utilise un nom de rôle reconnaissable et journalise l'identifiant de corrélation de manière interrogeable.
Azure Functions : OpenTelemetry et journaux structurés
Pour les fonctions .NET en modèle worker isolé, Microsoft documente désormais OpenTelemetry comme moyen d'envoyer la télémétrie de l'hôte et du worker vers Application Insights : définissez telemetryMode sur OpenTelemetry dans host.json et enregistrez dans Program.cs l'intégration OpenTelemetry du worker Functions avec l'exportateur Azure Monitor. La configuration est décrite dans Utiliser OpenTelemetry avec Azure Functions.
// host.json
{
"version": "2.0",
"telemetryMode": "OpenTelemetry"
}
// Program.cs (.NET isolated worker)
// Packages: Microsoft.Azure.Functions.Worker.OpenTelemetry, OpenTelemetry.Extensions.Hosting,
// Azure.Monitor.OpenTelemetry.Exporter, Azure.Identity, Azure.Messaging.ServiceBus
using Azure.Identity;
using Azure.Messaging.ServiceBus;
using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Azure.Functions.Worker.OpenTelemetry;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();
// The user-assigned identity of this integration (use DefaultAzureCredential for local development)
var credential = new ManagedIdentityCredential(builder.Configuration["UamiClientId"]);
builder.Services.AddOpenTelemetry()
.UseFunctionsWorkerDefaults()
.UseAzureMonitorExporter(options => options.Credential = credential); // Entra ID ingestion (worker)
builder.Logging.AddOpenTelemetry(options => options.IncludeScopes = true); // scopes are off by default
builder.Services.AddSingleton(new ServiceBusClient(
builder.Configuration["ServiceBusConnection__fullyQualifiedNamespace"], credential));
builder.Services.AddSingleton(sp =>
sp.GetRequiredService<ServiceBusClient>().CreateSender("erp-outbound"));
builder.Build().Run();Trois détails de cette documentation comptent pour les intégrations. Les étendues (scopes) de journalisation ne sont pas incluses par défaut : activez IncludeScopes ou placez l'identifiant de corrélation dans le modèle de message. L'identifiant d'opération d'une invocation déclenchée par Service Bus provient du traceparent du message entrant. Et si vous utilisez l'authentification Microsoft Entra pour l'ingestion, vous la configurez séparément pour l'hôte (le paramètre APPLICATIONINSIGHTS_AUTHENTICATION_STRING) et pour le worker (les informations d'identification de l'exportateur). Si vous préférez le SDK Application Insights classique, les mêmes principes s'appliquent.
Logic Apps : Standard ou Consommation
Les applications logiques Standard s'exécutent sur le runtime Azure Functions et peuvent envoyer de la télémétrie vers Application Insights. Avec la télémétrie améliorée activée dans host.json, les événements de déclencheurs et d'actions arrivent dans la table requests avec le nom du workflow, l'identifiant d'exécution et l'identifiant de suivi client dans les dimensions personnalisées ; l'identifiant d'opération est égal à l'identifiant d'exécution du workflow. Voir Télémétrie améliorée pour les workflows Standard.
// host.json of a Standard logic app: enhanced Application Insights telemetry
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle.Workflows",
"version": "[1, 2.00]"
},
"extensions": {
"workflow": {
"Settings": {
"Runtime.ApplicationInsightTelemetryVersion": "v2"
}
}
}
}Les applications logiques Consommation n'ont pas cette intégration Application Insights : vous ajoutez un paramètre de diagnostic avec la catégorie runtime de workflow vers un espace de travail Log Analytics, éventuellement avec la solution Logic Apps Management. En Standard, ce même type de paramètre de diagnostic écrit dans la table LogicAppWorkflowRuntime. Dans les deux cas, définissez le Custom Tracking Id du déclencheur sur l'identifiant de corrélation du message et utilisez des propriétés suivies pour les clés métier sur lesquelles vous recherchez. Microsoft précise que les journaux de ressources peuvent arriver avec des dizaines de minutes de retard et ne sont pas totalement exempts de pertes : utilisez-les pour le diagnostic, pas comme piste d'audit. Voir Collecter des données de diagnostic pour les workflows Logic Apps.
Service Bus : métriques, journaux de ressources et contexte de trace
Service Bus fournit sans configuration des métriques de plateforme par entité : messages actifs, messages en lettres mortes, messages entrants et sortants, erreurs serveur, erreurs utilisateur et requêtes limitées. Les nombres de messages en lettres mortes et de messages actifs ne peuvent pas être exportés vers Log Analytics via un paramètre de diagnostic : créez donc des alertes de métrique sur ceux-ci. Les journaux de ressources (journaux opérationnels, journaux d'erreurs de diagnostic, journaux de réseau virtuel et, uniquement au niveau Premium, journaux d'audit runtime) sont envoyés vers Log Analytics via un paramètre de diagnostic, dans AzureDiagnostics ou dans des tables spécifiques à la ressource. La référence est Référence des données de supervision pour Azure Service Bus.
Les journaux opérationnels ne capturent que les opérations de gestion, pas les envois ni les réceptions : le traçage au niveau des messages doit donc venir des clients. Le SDK Azure.Messaging.ServiceBus transporte le contexte de trace W3C dans la propriété d'application Diagnostic-Id ; c'est ainsi qu'une fonction qui envoie un message et la fonction qui le reçoit se retrouvent dans la même opération Application Insights. Voir Traçage de bout en bout et diagnostics dans Service Bus.
La cartographie d'application
Dès que les composants Azure envoient leur télémétrie avec des noms de rôle clairs, la cartographie d'application montre les étapes que votre code voit : la fonction, ses dépendances Service Bus et HTTP, et ses appels vers Dataverse. Ne vous attendez pas à y voir le plugin Dataverse : l'export de la plateforme vit dans sa propre ressource et n'est pas relié à vos fonctions par un contexte de trace. C'est précisément ce vide que comble l'identifiant de corrélation. Voir Cartographie d'application dans Application Insights.
Suivre une transaction métier à travers toutes les étapes
Le modèle que j'utilise est simple et ne dépend des capacités de traçage d'aucun produit en particulier :
- Le plugin prend le CorrelationId de Dataverse (ou un identifiant reçu d'une API en amont), l'écrit dans les variables partagées du contexte d'exécution et le journalise avec ILogger.
- La fonction le lit dans le corps du message, en marque l'invocation et l'inclut dans chaque ligne de journal.
- Chaque message que la fonction envoie en aval le porte comme CorrelationId Service Bus et comme propriété d'application.
- La Logic App l'utilise comme Custom Tracking Id et comme propriété suivie.
- Les appels vers le système cible le transmettent dans un en-tête, afin que l'équipe destinataire puisse rechercher la même valeur dans ses propres journaux.
La fonction : lire, marquer, propager
Une fonction worker isolé déclenchée par Service Bus, qui utilise le client et l'expéditeur enregistrés plus haut dans Program.cs. L'identifiant client de l'identité managée provient des paramètres d'application ; rien dans la configuration n'est un secret.
using System.Diagnostics;
using System.Text.Json;
using Azure.Messaging.ServiceBus;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
public class ErpSyncFunction
{
private readonly ILogger<ErpSyncFunction> _logger;
private readonly ServiceBusSender _erpSender;
public ErpSyncFunction(ILogger<ErpSyncFunction> logger, ServiceBusSender erpSender)
{
_logger = logger;
_erpSender = erpSender;
}
[Function(nameof(ErpSyncFunction))]
public async Task Run(
[ServiceBusTrigger("crm-account-events", "erp-sync", Connection = "ServiceBusConnection")]
ServiceBusReceivedMessage message)
{
// Body = the RemoteExecutionContext as JSON. Check the shape against a captured message first.
using var body = JsonDocument.Parse(message.Body.ToString());
var context = body.RootElement;
var correlationId = ReadSharedVariable(context, "crx_correlationid")
?? context.GetProperty("CorrelationId").GetString()!;
// Searchable on the request row and on every log line of this invocation
Activity.Current?.SetTag("crx.correlation_id", correlationId);
using var scope = _logger.BeginScope(new Dictionary<string, object> { ["CorrelationId"] = correlationId });
_logger.LogInformation("ERP sync started for {EntityName} {EntityId}",
context.GetProperty("PrimaryEntityName").GetString(),
context.GetProperty("PrimaryEntityId").GetString());
// ... map and validate. Throw on failure: Service Bus retries and eventually dead-letters.
var outbound = new ServiceBusMessage(BinaryData.FromObjectAsJson(new { correlationId /*, payload */ }))
{
CorrelationId = correlationId,
ContentType = "application/json"
};
outbound.ApplicationProperties["crxCorrelationId"] = correlationId;
await _erpSender.SendMessageAsync(outbound);
}
private static string? ReadSharedVariable(JsonElement context, string key)
{
if (!context.TryGetProperty("SharedVariables", out var variables)) return null;
foreach (var pair in variables.EnumerateArray())
{
if (pair.GetProperty("key").GetString() == key) return pair.GetProperty("value").ToString();
}
return null;
}
}Une requête pour suivre un identifiant de corrélation
Exécutez cette requête dans l'espace de travail Log Analytics dans lequel écrivent les deux ressources Application Insights et le paramètre de diagnostic Logic Apps. Elle trouve chaque opération qui mentionne l'identifiant, rassemble toute la télémétrie de ces opérations, ajoute les événements de la Logic App et trie le tout chronologiquement.
// Log Analytics workspace shared by both Application Insights resources and the Logic Apps diagnostic setting
let cid = "6f1c2d3e-5a4b-4c7d-9e8f-0a1b2c3d4e5f";
let lookback = ago(3d);
// 1. Every operation (plug-in, function invocation) that mentions the ID
let ops = union AppTraces, AppRequests, AppExceptions
| where TimeGenerated > lookback
| where Message has cid or tostring(Properties) has cid
| distinct OperationId;
// 2. All Dataverse and Function telemetry belonging to those operations
let appTelemetry = union AppRequests, AppDependencies, AppTraces, AppExceptions
| where TimeGenerated > lookback and OperationId in (ops)
| project TimeGenerated, Hop = AppRoleName, Kind = Type,
Detail = coalesce(Name, Message, OuterMessage), Success, DurationMs, OperationId;
// 3. Logic App events that carry the ID as client tracking ID
let workflowEvents = LogicAppWorkflowRuntime
| where TimeGenerated > lookback and ClientTrackingId == cid
| project TimeGenerated, Hop = WorkflowName, Kind = OperationName,
Detail = coalesce(ActionName, TriggerName, Error), Success = Status != "Failed",
DurationMs = real(null), OperationId = RunId;
union appTelemetry, workflowEvents
| order by TimeGenerated ascEnregistrez-la comme requête partagée ou comme classeur avec l'identifiant de corrélation en paramètre, et donnez au support un accès en lecture à l'espace de travail. La première fois que quelqu'un répond à la question « où est ma mise à jour ? » en deux minutes sans escalade, l'effort est rentabilisé.
Les alertes qui valent la peine
Je limite la première série d'alertes et j'envoie chacune à l'équipe capable d'agir :
| Signal | Source | Règle |
|---|---|---|
| Messages en lettres mortes | Métrique Service Bus DeadletteredMessages, répartie par entité | Maximum supérieur à 0 sur 5 minutes. Chaque message en lettres mortes est une modification métier qui n'est pas arrivée. |
| Arriéré croissant | Métrique Service Bus ActiveMessages, répartie par entité | Au-dessus d'un seuil fondé sur votre volume habituel pendant 15 minutes ou plus : le consommateur est arrêté, limité ou trop lent. |
| Exécutions de fonctions en échec | Alerte de journal sur AppRequests dans l'Application Insights de l'intégration | Toute requête avec Success égal à false, regroupée par fonction, par tranche de 5 minutes. |
| Exécutions de workflow en échec | Consommation : la métrique Runs Failed. Standard : une alerte de journal sur LogicAppWorkflowRuntime | Toute exécution qui se termine avec le statut Failed. |
| Échecs de plugins | AppDependencies (type Plugin) et AppExceptions issus de l'export Dataverse | À utiliser pour les tendances et une revue quotidienne plutôt que pour réveiller quelqu'un, vu le niveau de service de livraison documenté de 24 heures. |
| Latence et silence | Durée des requêtes de fonctions et nombre d'exécutions réussies | 95e centile au-dessus du délai de traitement convenu avec le métier, et zéro exécution réussie pendant les heures ouvrables lorsque vous attendez du trafic. |
// Failed function executions (integration Application Insights / workspace) AppRequests | where Success == false | summarize Failures = count() by AppRoleName, OperationName, bin(TimeGenerated, 5m) // Failed Standard workflow runs (Logic Apps diagnostic setting) LogicAppWorkflowRuntime | where OperationName has "WorkflowRunCompleted" and Status == "Failed" | summarize FailedRuns = count() by WorkflowName, bin(TimeGenerated, 5m) // Failing plug-ins (Dataverse export, for trends rather than paging) AppDependencies | where DependencyType == "Plugin" and Success == false | summarize Failures = count() by Name, bin(TimeGenerated, 1h)
Envoyez chaque alerte vers un groupe d'actions avec un responsable nommé, et ajoutez un lien vers la requête de corrélation dans la description de l'alerte. Une alerte dont personne n'est responsable devient du bruit en quelques semaines.
Se passer de secrets grâce aux identités managées
La supervision vous dit quand une intégration casse. Les identités managées suppriment l'une des causes les plus fréquentes : un secret expiré ou divulgué. Une identité managée est une identité Microsoft Entra dont Azure gère les informations d'identification pour vous. Voir Que sont les identités managées pour les ressources Azure ?.
Affectée par le système ou par l'utilisateur ?
- Affectée par le système : créée et supprimée avec la ressource, une par ressource. Simple, mais recréer la ressource vous donne un nouveau principal, et vous ne pouvez ni attribuer de rôles ni créer l'utilisateur d'application Dataverse avant que la ressource n'existe.
- Affectée par l'utilisateur : une ressource autonome que vous créez une fois et associez à une ou plusieurs applications. Vous pouvez attribuer des rôles et l'enregistrer dans Dataverse avant tout déploiement de code, et elle survit aux redéploiements.
Pour les intégrations, j'utilise en général une identité affectée par l'utilisateur par intégration et par environnement : les droits de la synchronisation ERP de production sont ainsi visibles au même endroit et jamais partagés avec l'environnement de test. L'exception est une application logique Standard qui utilise des connecteurs intégrés de type fournisseur de services, comme Service Bus : Microsoft indique que la plupart d'entre eux ne prennent actuellement en charge que l'identité affectée par le système.
Functions vers Service Bus, Storage et Application Insights sans chaîne de connexion
Une connexion basée sur l'identité dans Functions est un groupe de paramètres d'application partageant un préfixe. Pour Service Bus, vous définissez <préfixe>__fullyQualifiedNamespace et, pour une identité affectée par l'utilisateur, <préfixe>__credential avec la valeur managedidentity et <préfixe>__clientId. Le compte de stockage de l'hôte et Application Insights disposent de paramètres équivalents. Microsoft les documente dans Configurer les connexions dans Azure Functions.
# Function app settings: identifiers only, no secrets ServiceBusConnection__fullyQualifiedNamespace = sb-crx-integration.servicebus.windows.net ServiceBusConnection__credential = managedidentity ServiceBusConnection__clientId = <client ID of id-crx-erp-sync> AzureWebJobsStorage__accountName = stcrxerpsync AzureWebJobsStorage__credential = managedidentity AzureWebJobsStorage__clientId = <client ID of id-crx-erp-sync> APPLICATIONINSIGHTS_CONNECTION_STRING = InstrumentationKey=...;IngestionEndpoint=... APPLICATIONINSIGHTS_AUTHENTICATION_STRING = ClientId=<client ID of id-crx-erp-sync>;Authorization=AAD UamiClientId = <client ID of id-crx-erp-sync> DataverseUrl = https://yourorg.crm4.dynamics.com ErpApiKey = @Microsoft.KeyVault(SecretUri=https://kv-crx-integration.vault.azure.net/secrets/erp-api-key/) # Role assignments at the narrowest scope (Azure CLI) MI=$(az identity show -g rg-crx-integration -n id-crx-erp-sync --query principalId -o tsv) SB=$(az servicebus namespace show -g rg-crx-integration -n sb-crx-integration --query id -o tsv) az role assignment create --assignee-object-id $MI --assignee-principal-type ServicePrincipal \ --role "Azure Service Bus Data Receiver" --scope "$SB/topics/crm-account-events/subscriptions/erp-sync" az role assignment create --assignee-object-id $MI --assignee-principal-type ServicePrincipal \ --role "Azure Service Bus Data Sender" --scope "$SB/queues/erp-outbound" az role assignment create --assignee-object-id $MI --assignee-principal-type ServicePrincipal \ --role "Key Vault Secrets User" \ --scope $(az keyvault show -g rg-crx-integration -n kv-crx-integration --query id -o tsv) az role assignment create --assignee-object-id $MI --assignee-principal-type ServicePrincipal \ --role "Monitoring Metrics Publisher" \ --scope $(az monitor app-insights component show -g rg-crx-integration -a appi-crx-integration --query id -o tsv) # Key Vault references must use the user-assigned identity, not the system-assigned one az functionapp update -g rg-crx-integration -n func-crx-erp-sync \ --set keyVaultReferenceIdentity=$(az identity show -g rg-crx-integration -n id-crx-erp-sync --query id -o tsv)
Attribuez les rôles à la portée la plus étroite possible : Azure Service Bus Data Receiver sur l'abonnement de rubrique écouté par la fonction, et Azure Service Bus Data Sender sur la file ou la rubrique dans laquelle elle écrit. Un piège signalé dans la documentation du déclencheur Service Bus : pour une mise à l'échelle précise, l'extension lit les propriétés runtime de l'entité, ce qui exige le rôle Azure Service Bus Data Owner ou un rôle personnalisé incluant Microsoft.ServiceBus/namespaces/*/read. Sans lui, aucune erreur au démarrage : la mise à l'échelle se rabat silencieusement sur une estimation moins précise. Les attributions de rôle peuvent mettre jusqu'à cinq minutes à se propager, et le portail Azure ne peut pas attribuer de rôle au niveau d'un abonnement de rubrique : utilisez donc la CLI ou Bicep. Voir Utiliser des identités managées pour accéder à Azure Service Bus.
Logic Apps vers Service Bus et Key Vault
Logic Apps prend en charge les identités managées dans les deux plans d'hébergement : les connecteurs managés Service Bus et Key Vault ainsi que l'action HTTP intégrée dans les deux plans, plus le connecteur Service Bus intégré en Standard. Une application logique peut avoir plusieurs identités, mais chaque connexion n'en utilise qu'une à la fois. Voir S'authentifier avec des identités managées dans Azure Logic Apps.
Functions et Logic Apps qui appellent Dataverse en tant qu'identité managée
Dataverse accepte une identité managée comme utilisateur d'application. Dans le centre d'administration Power Platform, ouvrez l'environnement, allez dans Paramètres > Utilisateurs + autorisations > Utilisateurs d'application, choisissez Nouvel utilisateur d'application et saisissez l'identifiant d'application de l'identité managée (pas son nom). Attribuez ensuite une division et un rôle de sécurité aux droits minimaux. Voir Gérer les utilisateurs d'application dans le centre d'administration Power Platform.
Dans une fonction, donnez au ServiceClient Dataverse un fournisseur de jetons qui demande à l'identité managée un jeton pour l'URL de l'environnement. Ce code prolonge le Program.cs présenté plus haut :
// Program.cs: Dataverse as the managed identity (registered as an application user)
// Packages: Microsoft.PowerPlatform.Dataverse.Client, Azure.Identity
using Azure.Core;
using Microsoft.PowerPlatform.Dataverse.Client;
var dataverseUrl = new Uri(builder.Configuration["DataverseUrl"]!);
var dataverseScope = $"{dataverseUrl.GetLeftPart(UriPartial.Authority)}/.default";
builder.Services.AddSingleton(sp => new ServiceClient(
dataverseUrl,
async _ => (await credential.GetTokenAsync(
new TokenRequestContext(new[] { dataverseScope }), CancellationToken.None)).Token,
useUniqueInstance: true,
logger: sp.GetRequiredService<ILogger<ServiceClient>>()));Pour Logic Apps, il y a un piège : le connecteur Microsoft Dataverse répertorie actuellement OAuth, un principal de service avec secret client et un certificat client comme types d'authentification, mais pas l'identité managée. Lorsque je veux qu'une application logique appelle Dataverse sans secret, j'utilise l'action HTTP sur la Web API Dataverse avec l'authentification par identité managée et l'URL de l'environnement comme audience, puis j'enregistre cette identité comme utilisateur d'application comme décrit ci-dessus. Voir les types d'authentification dans la référence du connecteur Microsoft Dataverse.
Identité managée pour les plugins Dataverse
Les plugins peuvent obtenir des jetons pour des ressources Azure sans informations d'identification stockées grâce à l'identité managée Power Platform. Microsoft recommande la version 2 de la configuration, qui comprend les étapes suivantes :
- Créez une identité managée affectée par l'utilisateur ou une inscription d'application dans Microsoft Entra ID et notez l'identifiant client et l'identifiant de locataire.
- Appelez IManagedIdentityService.AcquireToken dans le plugin, signez l'assembly (ou le package de plugin) avec un certificat et enregistrez-le.
- Sur l'identité, ajoutez des informations d'identification fédérées avec l'émetteur https://login.microsoftonline.com/{tenantId}/v2.0 et un identifiant de sujet construit à partir de l'identifiant de locataire encodé, de l'identifiant d'environnement et des hachages de l'émetteur et du sujet du certificat.
- Créez via la Web API un enregistrement managedidentity dans Dataverse en version 2 et liez-y l'assembly ou le package de plugin.
- Accordez à l'identité l'accès à la ressource Azure, par exemple le rôle Key Vault Secrets User sur un coffre de clés.
- Déclenchez le plugin et vérifiez qu'il obtient un jeton.
// Inside Execute(): read a secret from Key Vault as the plug-in's managed identity
var identityService = (IManagedIdentityService)serviceProvider.GetService(typeof(IManagedIdentityService));
var token = identityService.AcquireToken(new[] { "https://vault.azure.net/.default" });
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(15);
client.DefaultRequestHeaders.ConnectionClose = true; // KeepAlive false, per plug-in guidance
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = client
.GetAsync("https://kv-crx-integration.vault.azure.net/secrets/erp-api-key?api-version=7.4")
.GetAwaiter()
.GetResult();
response.EnsureSuccessStatusCode();
// Read "value" from the JSON body. Never trace or log the secret itself.
}Connaissez les limites avant de compter dessus. Les certificats auto-signés sont réservés au développement et aux tests. L'identifiant de sujet contient l'identifiant d'environnement : chaque environnement a donc besoin de ses propres informations d'identification fédérées et de son propre enregistrement managedidentity, qui ont leur place dans vos scripts de déploiement plutôt que dans la mémoire de quelqu'un. Les commandes d'identité managée de la CLI Power Platform ne fonctionnent pas avec les identités affectées par l'utilisateur ni sous Linux. Le guide de configuration est Configurer l'identité managée Power Platform pour les plugins Dataverse.
Ce qui nécessite encore un secret, et où le conserver
Se passer totalement de secrets est rarement possible dès le premier jour. Voici ceux que je rencontre encore le plus souvent :
- Le point de terminaison de service Dataverse vers Service Bus : la méthode documentée pour que Dataverse publie vers Service Bus reste une signature d'accès partagé. Utilisez une stratégie dédiée avec uniquement le droit Send sur cette seule rubrique, et renouvelez-la. Le schéma de la table ServiceEndpoint affiche désormais un type d'authentification Managed Identity et des colonnes liées à Key Vault, mais je n'ai trouvé aucune procédure de configuration documentée : je ne m'y fie donc pas encore en production.
- Systèmes cibles hors de Microsoft Entra ID : clés d'API ou secrets clients OAuth pour un ERP ou l'API d'un partenaire. Stockez-les dans Key Vault et référencez-les depuis les paramètres d'application avec @Microsoft.KeyVault(...), que l'application résout avec son identité managée.
- Le connecteur Dataverse dans Logic Apps : si vous l'utilisez à la place de l'action HTTP, son secret client ou son certificat a sa place dans Key Vault, avec un responsable et une date d'expiration dans l'agenda de quelqu'un.
- Plugins qui appellent des API non Azure : au lieu de placer une clé dans la configuration sécurisée d'une étape, laissez l'identité managée du plugin la lire dans Key Vault à l'exécution, et ne l'écrivez jamais dans une trace.
- Webhooks entrants : les webhooks Dataverse s'authentifient avec une clé, un en-tête HTTP ou une chaîne de requête : une fonction à déclencheur HTTP derrière un webhook a donc besoin d'une clé de fonction ou d'un équivalent. Conservez-la dans Key Vault et dans l'enregistrement du webhook, nulle part ailleurs.
Pour Key Vault lui-même, utilisez le modèle d'autorisation Azure RBAC et attribuez à chaque identité le rôle Key Vault Secrets User sur le coffre (ou sur des secrets individuels). Lorsqu'une application de fonction ou une application logique Standard résout des références Key Vault avec une identité affectée par l'utilisateur, définissez keyVaultReferenceIdentity sur l'application ; sinon, elle utilise l'identité affectée par le système. Voir Utiliser des références Key Vault dans App Service et Azure Functions.
Liste de contrôle pour le déploiement
- Dessinez les étapes de chaque intégration avec un responsable par étape, et convenez du nom et du format de l'identifiant de corrélation.
- Créez un espace de travail Log Analytics par niveau d'environnement avec Application Insights basé sur l'espace de travail : une ressource par environnement Dataverse pour l'export de la plateforme, et une pour les composants Azure.
- Vérifiez les licences avant d'activer les environnements gérés, puis configurez l'export Application Insights avec un compte disposant de tous les rôles requis, et prévoyez 24 heures pour les premières données.
- Ajoutez ILogger à côté d'ITracingService dans les plugins du chemin d'intégration, et laissez la journalisation des traces de plugin sur Exception en production.
- Enregistrez le point de terminaison de service avec JSON comme format de message et publiez via un plugin qui ajoute l'identifiant de corrélation.
- Activez OpenTelemetry dans Functions, journalisez des valeurs structurées, marquez l'invocation et propagez l'identifiant dans chaque message sortant et chaque appel HTTP.
- Activez la télémétrie améliorée (Standard) ou un paramètre de diagnostic (Consommation) pour Logic Apps, et définissez le Custom Tracking Id et les propriétés suivies.
- Ajoutez des paramètres de diagnostic Service Bus et des alertes de métrique sur les lettres mortes et l'arriéré, ainsi que des alertes de journal sur les fonctions et exécutions de workflow en échec.
- Créez une identité affectée par l'utilisateur par intégration et par environnement, attribuez les rôles au niveau de l'entité, enregistrez-la comme utilisateur d'application Dataverse avec un rôle aux droits minimaux, et déplacez chaque secret restant dans Key Vault.
- Enregistrez la requête de corrélation comme classeur, puis cassez volontairement quelque chose en test et vérifiez que le support peut le tracer en quelques minutes.
Pièges fréquents
- Échantillonner la preuve. Les intégrations ont souvent de faibles volumes où chaque message compte. Vérifiez vos paramètres d'échantillonnage : avec l'échantillonnage basé sur le parent, la documentation indique qu'aucune télémétrie de requête n'est générée lorsque le message entrant n'est pas échantillonné.
- Désactiver l'authentification locale sur la mauvaise ressource. L'export Dataverse a besoin de l'authentification locale sur sa ressource Application Insights. Imposez l'ingestion exclusivement via Entra sur la ressource côté Azure.
- Désactiver SAS trop tôt sur l'espace de noms Service Bus. Si le point de terminaison de service Dataverse utilise encore une signature d'accès partagé, désactiver l'authentification locale sur l'espace de noms empêche Dataverse de publier. Recensez d'abord chaque consommateur de SAS.
- Une ressource Application Insights pour plusieurs environnements Dataverse. Selon Microsoft, les rapports standard ne fonctionnent alors pas correctement, et mélanger la télémétrie de test et de production complique chaque requête.
- Attendre de la télémétrie Dataverse en temps réel. Le niveau de service de livraison documenté est de 24 heures. Déclenchez les alertes sur les signaux côté Azure et utilisez l'export Dataverse pour le diagnostic et les tendances.
- Des droits Receiver seuls pour une fonction qui doit monter en charge. Sans le droit de lecture décrit plus haut, la mise à l'échelle devient silencieusement moins précise. Pour les applications logiques Standard, rappelez-vous aussi que la plupart des connecteurs intégrés ne fonctionnent qu'avec l'identité affectée par le système.
- Journaliser des données personnelles. Application Insights est lu par plus de personnes que le journal des traces de plugin, dont les colonnes sensibles sont réservées aux administrateurs système. Journalisez des identifiants d'enregistrement et de corrélation, pas des noms, adresses e-mail ou contenus de messages ; au regard du RGPD, la télémétrie est aussi un traitement de données personnelles.
- Oublier la durée de vie des jetons et le délai de propagation. Les nouvelles attributions de rôle peuvent mettre jusqu'à cinq minutes à s'appliquer, et après le retrait d'un rôle, une application en cours d'exécution peut continuer à utiliser son jeton Service Bus jusqu'à son expiration, ce qui peut prendre jusqu'à 24 heures. Redémarrez l'application lorsque vous révoquez un accès.
Conclusion
La supervision de bout en bout d'une intégration Dataverse n'est pas un produit qu'on active. C'est une poignée de décisions appliquées avec constance : exporter ce que Dataverse propose, écrire votre propre télémétrie là où la plateforme s'arrête, transporter un identifiant de corrélation à travers chaque étape et créer des alertes sur les quelques signaux qui indiquent qu'une modification métier n'est pas arrivée. Les identités managées éliminent ensuite toute une catégorie d'incidents. Commencez par une intégration, rendez-la traçable de Dynamics 365 jusqu'au système cible, et servez-vous-en comme modèle pour les autres.