Logging and Diagnostics for Dynamics 365 Plug-ins You Cannot Debug in Production

A technical guide for ISVs, partners and consultants whose plug-ins fail in customer environments: correlation IDs, what the trace log keeps, Application Insights, logging without latency, customer self-service capture and a multi-tenant triage workflow.

When your plug-ins fail in a customer's Dynamics 365 environment and you cannot attach a debugger, build the visibility into the code. Write one short, structured trace line per decision through ITracingService, stamped with the execution context CorrelationId, so a user action can be followed through every plug-in and asynchronous job it sets off. Treat the plug-in trace log as short lived and limited: it only records what the customer's setting allows and is cleared automatically. Send telemetry to Application Insights where the customer has enabled it and longer evidence matters. Never make synchronous logging calls to external services. Give customers a documented capture routine, then triage every ticket by correlation ID, solution version and exception signature.

Why is a plug-in in a customer environment so hard to diagnose?

Because almost every tool a developer relies on is missing. Online plug-ins run in the sandbox on Microsoft infrastructure, so there is no debugger to attach to the live process and no server log to read. Plug-in Profiler replays can help, but they need someone with rights in that environment to capture a profile, and they reproduce one execution rather than the conditions around it. The environment belongs to the customer, so the trace log setting, the security roles, the data and the other solutions installed next to yours are all outside your control. The error the user saw has usually been closed and forgotten by the time the ticket reaches you.

For an ISV or a partner supporting many tenants this multiplies. The same assembly behaves differently because one customer has a flow writing to the same table, another has an older version of your managed solution, and a third has the trace log switched off. The only evidence you can rely on is evidence your code produced itself, which is why this page is about what the plug-in writes before it fails. How to read that evidence when faults appear only under load, such as races, the sandbox timeout and ordering problems, is covered in our guide to diagnosing intermittent Dynamics 365 failures and is not repeated here.

How do you tie one user action to every plug-in it triggers?

Use the identifiers the platform already gives you, and write them into every trace line. The execution context carries a CorrelationId that is shared by the operations one original request sets off, including plug-ins registered on other tables and asynchronous jobs queued from it. It also carries a RequestId, an OperationId, the Depth, the MessageName, the PrimaryEntityName and PrimaryEntityId, the UserId and InitiatingUserId, the OrganizationId and, on the plug-in context, the Stage and Mode. Together they tell you which save caused this execution, how far down the chain it is and whose permissions it ran under.

To reach back to the user action itself, let the caller label the request. Dataverse lets a client pass an optional tag value with a Web API or SDK request, which appears to plug-ins as a shared variable. A form script, a canvas app or an integration can send its own action identifier this way, and your plug-in writes it next to the CorrelationId. Check the current Microsoft documentation for the exact parameter syntax before relying on it. Where a caller cannot set a tag, log the identifiers the caller can see, such as the record ID and the time in UTC, so support can join the two sides.

Field in every trace lineWhere it comes fromWhy support needs it
Correlation IDIExecutionContext.CorrelationIdJoins every plug-in and async job caused by one original request.
Caller tag, if presentThe tag shared variable passed with the requestJoins the server evidence to the button, screen or integration run that started it.
Assembly and solution versionA constant compiled into your assemblyTells you at once whether the customer runs the build you think they run.
Plug-in stepClass name, message, primary table, stage, mode, depthShows where in the pipeline this line ran and whether it is a cascade.
Record and userPrimaryEntityId, UserId, InitiatingUserIdLets the customer find the record and lets you test with the same security role.
Decision and elapsed timeYour code, with a stopwatch around external or heavy callsShows which branch was taken and which step consumed the time.

What should a structured trace line from a plug-in contain?

Short, consistent key and value pairs rather than prose, written through ITracingService, which you obtain from the service provider at the start of Execute. Write one header line on entry with the fields in the table above, one line per decision the code takes, such as "branch=skip reason=statusUnchanged", and one line with the elapsed time on exit. Keep the format identical across every plug-in you ship, so a support engineer, a script or a query can read any customer's traces the same way.

What you leave out matters as much. Do not write whole records, column values that hold personal data, tokens, connection strings or secure configuration into a trace. Trace rows are readable by administrators in the customer's environment, they may be exported into tickets, and under GDPR they are personal data processing like anything else. Log the record ID and the names of the columns that drove a decision, not their contents. When the plug-in throws, put the correlation ID and the version into the InvalidPluginExecutionException message as well, because the user sees that message and can quote it even when no trace row survives.

The design faults that make plug-ins hard to trace in the first place, such as logic spread across plug-ins, workflows and scripts with no single owner, are catalogued in our Dynamics 365 customisation mistakes guide.

What will the plug-in trace log keep, and what will it lose?

The plug-in trace log is useful and easy to over trust. It is a table in the customer's environment, written from what your code passed to ITracingService, and it behaves in ways that surprise teams the first time a trace they expected is not there. Retention defaults, size limits and the privileges needed to read the table change over time, so confirm current values in the Microsoft documentation for the plug-in trace log.

BehaviourWhat it means for you
The environment setting is Off, Exception or All, and the customer controls it.With Off nothing is written. With Exception a row is written only when an exception leaves the plug-in. All writes a row for every execution and belongs in a planned diagnostic window, not permanently in production.
A swallowed exception writes nothing under Exception.If your code catches an error, logs it and carries on, the trace is lost on the setting most production environments use. Decide deliberately which failures should throw.
Rows are cleared automatically by a system bulk deletion job, by default after a short period.It is not an archive. Anything needed for a ticket has to be exported the same day, which is why the capture routine below exists.
Each row holds a limited amount of trace text.A plug-in that writes long loops of detail can lose part of its output. Keep lines short and write the identifying header first.
Asynchronous steps write their trace too, and the System Jobs record carries the error message.For async failures read both, joined on the correlation ID.
Reading the table needs suitable privileges in the customer environment.Your support engineer usually cannot see it directly, so the customer or their partner has to export it.

When should plug-in telemetry go to Application Insights instead?

When you need evidence that lasts longer than the trace log, successful executions as well as failures, timings you can chart, or alerts that fire before the customer calls. Dataverse can export telemetry for an environment to an Azure Application Insights resource, configured by an administrator in the Power Platform admin center, and Microsoft provides an ILogger interface for plug-ins that writes into that telemetry alongside the platform's own records of API calls and plug-in executions. Availability, licensing and Managed Environments requirements for this export have changed more than once, so check the current Microsoft documentation before designing around it.

For an ISV there is an ownership question underneath. The Application Insights resource belongs to the customer, in their subscription, so your support team sees it only if the customer grants access. Sending telemetry from the plug-in to your own external endpoint instead moves data out of the customer's tenant, which needs their explicit agreement and a data processing position, and it adds a network call to every execution unless it is done asynchronously. In practice most partners use a mix: ITracingService everywhere, ILogger where the customer exports to Application Insights, and an agreed access arrangement written into the support contract.

NeedPlug-in trace logApplication Insights export
Evidence for one failing case this weekGood, if exported the same day.Good, if the export was enabled before the failure.
History across weeks and releasesNo, rows are cleared automatically.Yes, subject to the retention set on the resource.
Successful executions and timingsOnly with All, which is not a production setting.Yes.
Alerts before the customer noticesNo.Yes, with queries and alert rules on the resource.
Who controls accessThe customer environment administrator.The owner of the Azure subscription, usually the customer.

How do you log from a plug-in without slowing every save?

Tracing through ITracingService is cheap next to a network call, and the platform persists it for you, so the cost to watch is everything else a well meaning logging layer adds. A synchronous plug-in runs inside the user's save, and its whole execution counts against the sandbox time limit, so every millisecond of logging is a millisecond the user waits.

  • Never call an external logging endpoint synchronously from a synchronous step. If telemetry must leave the environment, post it from an asynchronous step, or hand the execution context to Azure Service Bus through a registered service endpoint and process it outside the save.
  • Do not write log rows to your own Dataverse table inside the pipeline transaction. When the plug-in fails, the transaction rolls back and the log row that would have explained the failure is rolled back with it.
  • Build trace strings cheaply: no serialising whole entities, no retrieves made only for logging, and verbose detail behind a flag that is off by default.
  • Time only the calls that can be slow, such as retrieves of many rows and external requests, rather than wrapping every line.
  • Keep logging helpers stateless. A logger stored in a class field of a cached plug-in instance can mix lines from concurrent executions.
  • Measure it. Compare step durations in a sandbox with verbose tracing on and off before shipping, and keep the result with the release notes.

How can a customer capture a failing case for you without a developer?

Give them a written routine that an administrator can follow in minutes, and build the switches it needs into your solution. The aim is that the first report of a failure arrives with evidence, rather than a screenshot and a week of back and forth.

  • Error details. Ask the user to copy the error message, which carries your correlation ID and version if you put them there, and to download the log file from the error dialog.
  • A time window. The exact time of the failure with time zone, the record and the user, so every other source can be filtered to that moment.
  • A diagnostic switch. Ship an environment variable or configuration row in your solution that raises your own trace verbosity, ideally with an expiry time so it switches itself off, and document it for the customer administrator.
  • A planned trace window. The administrator sets the trace log to All, the user repeats the action, and the administrator sets it back, following the steps you documented.
  • An export. Filter the plug-in trace log view by time or correlation ID and export it, together with any failed System Jobs for the record. A small model-driven page in your solution that does this filter and export for a pasted correlation ID removes most of the friction.
  • A Monitor session, when the problem starts on the form. Power Apps Monitor records form events and network requests, and the session can be downloaded and sent to you.
  • The installed version. The version of your managed solution from the solutions list, which settles half of all tickets on its own.

What triage workflow works for a support team with many customer environments?

One shared intake format and a fixed order of questions, so the same failure in two tenants is recognised as one problem rather than investigated twice. Senior consultants and full-stack developers at Solzet run plug-in support this way for customers directly and, white-label, for Microsoft partners and ISVs who keep the customer relationship under their own brand, as described on our Dynamics 365 subcontracting and white-label service.

StepQuestionWhat settles it
1. IntakeIs the evidence complete?Environment, solution version, correlation ID, time in UTC, record, user role, error text and trace export. Incomplete tickets go back with the capture routine attached.
2. VersionIs this a build with a known defect?Compare the solution version with the release register. If it is fixed in a later version, the answer is an upgrade.
3. SignatureHave we seen this failure elsewhere?Exception type, plug-in class, message, table and the first frame of your own code, matched against a known issues list across all tenants.
4. EnvironmentIs it our code or its neighbours?Other steps, flows and solutions on the same table and message in that environment, and the security role of the user who failed.
5. ReproduceCan we make it fail on purpose?A sandbox with the same version, the same role and the same payload. Load related faults follow the intermittent failures method.
6. Fix and releaseDoes the fix reach every affected tenant?A versioned managed solution through the same release path for every customer, never a hotfix built on one machine.
7. ConfirmHas the signature stopped?The same query on correlation IDs, trace exports or Application Insights after release, then close the known issue.

Is poor visibility a reason to move away from Dynamics 365?

Rarely on its own. Every hosted platform limits access to its servers, and plug-ins that log properly are diagnosable without a debugger. The fix is in the code, the capture routine and the support contract, not the platform. We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. Where the customer's real constraint is that they need full control of the servers, logs and data retention without Microsoft licensing, a custom-built CRM on React, Node.js, PostgreSQL or .NET is the alternative we build.

How does Solzet add diagnostics to plug-ins already in production?

We start with the evidence you already have: the plug-ins, their registered steps and a sample of recent tickets. Then we add a shared tracing helper with the fields above, correlation and version in every exception message, the diagnostic switch and the capture routine, and ILogger where customers export to Application Insights. We ship it as a normal versioned managed solution release, measure the latency cost in a sandbox, and hand over the triage format and the known issues register. Solzet delivers remotely from Yerevan, Armenia, with 8+ years of Dynamics 365 Customer Engagement and Power Platform work. The C# plug-in and Dataverse development side is on our Dynamics 365 development service.

What do people ask us?

How do you debug a Dynamics 365 plug-in in a customer production environment?

Usually you cannot attach a debugger, so you rely on evidence the plug-in writes itself. Trace one structured line per decision through ITracingService with the correlation ID and your assembly version, put both into the exception message, ask the customer to export the plug-in trace log rows for the failure time, and reproduce in a sandbox with the same solution version, security role and payload. Plug-in Profiler can help where the customer allows a profile to be captured.

How long does Dynamics 365 keep plug-in trace log records?

Not long. Plug-in trace log rows are removed automatically by a system bulk deletion job, by default after a short period, and administrators can change that job. Check the current Microsoft documentation for the default. Treat the trace log as a short term buffer: export rows for any failure you need to investigate the same day, and use Application Insights or your own ticket attachments for anything that must last.

Why is there no plug-in trace log row for an error a user reported?

The most common reasons are that the environment setting is Off, that it is set to Exception and your code caught the error without rethrowing it, that the rows were already cleared by the automatic clean-up, or that the person looking lacks privileges to read the table. A log row written to your own table inside the failing transaction is also rolled back. Put the correlation ID and version in the exception message so the user can quote them.

What is the CorrelationId in a Dynamics 365 plug-in?

It is an identifier on the plug-in execution context that is shared by the operations one original request sets off, including plug-ins on other tables and asynchronous jobs queued from it. Writing it into every trace line lets you filter the trace log and System Jobs down to the chain of work caused by one save. The context also carries RequestId, OperationId and Depth, which show where in that chain an execution sits.

Can Dynamics 365 plug-ins log to Application Insights?

Yes, where the environment exports its telemetry to Application Insights. An administrator configures the export in the Power Platform admin center, and plug-ins can write through the ILogger interface Microsoft provides, alongside the platform's own telemetry. Requirements for this export, including licensing and Managed Environments, have changed over time, so check current Microsoft documentation. The resource normally belongs to the customer, so agree support access in advance.

Does tracing slow down Dynamics 365 plug-ins?

Tracing through ITracingService is cheap compared with network calls, and the platform stores it for you. What slows saves is logging design around it: synchronous calls to external logging services, retrieves made only to log, serialising whole records, or writing log rows to Dataverse in the transaction. Keep synchronous steps to short trace lines, move external telemetry to asynchronous processing, and measure step duration with verbose logging on and off.

How should an ISV support plug-in failures across many customer tenants?

Standardise the evidence and the order of triage. Ship the same tracing format, a diagnostic switch and a capture routine in every release, require the solution version and correlation ID on each ticket, match failures by exception signature across tenants, reproduce in a sandbox with the same version and security role, and release fixes as versioned managed solutions through one path for every customer. Keep a known issues register so repeated failures are recognised immediately.

Which solution is right for your business?

Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.