Logging uses ILogger<T> via Microsoft.Extensions.Logging (MEL) with ZLogger as the provider. All business code injects ILogger<T> through DI; a legacy LoggerTraceListener bridge remains for third-party/WPF trace sources only.
Last updated: 2026-09-17
| Area | Path |
|---|---|
| Shared logging library | source/DevTools.Logging/ |
| Startup crash timeline | source/DevTools.Logging/Diagnostics/StartupTrace.cs |
| Monitor pane (Scintilla / WPF trace) | source/DevTools.Presentation/Logging/ |
| Shared presentation contracts | source/DevTools.Presentation/Interfaces/ |
| Revit logging lifecycle | source/RevitDevTool/Logging/LoggingService.cs |
| Revit enrichers/linkify/geometry listener | source/RevitDevTool/Logging/ |
| AutoCAD logging lifecycle | source/AcadDevTool/Logging/LoggingService.cs |
| AutoCAD enrichers | source/AcadDevTool/Logging/ |
All services, providers, and view models use DI-injected ILogger<T> with ZLogger extension methods:
public sealed class MyService(ILogger<MyService> logger)
{
public void DoWork()
{
logger.ZLogInformation($"Starting work...");
logger.ZLogError($"Something failed: {ex.Message}");
}
}For static utility classes that cannot use DI, pass an optional ILogger? logger = null parameter:
internal static class MyHelper
{
public static void DoStatic(string input, ILogger? logger = null)
{
logger?.ZLogDebug($"Processing {input}");
}
}flowchart TB
Services["ILogger<T> (DI-injected)"]
ZLogger["ZLogger Provider"]
Monitor["MonitorLogTarget"]
File["FileLogProcessor"]
Http["HttpLogProcessor"]
ThirdParty["Third-party Trace / WPF"]
Listener["LoggerTraceListener (bridge)"]
Detector["LogLevelDetector"]
Notify["NotifyListener"]
Services --> ZLogger
ZLogger --> Monitor
ZLogger --> File
ZLogger --> Http
ThirdParty --> Listener
Listener --> Detector
Detector --> ZLogger
Listener --> Notify
DevTools.Logging owns the headless pipeline:
- ZLogger provider registration (
LoggingExtensions.AddLoggingProvider()— config, notify, file, HTTP; no monitor) LoggerTraceListener(bridge for third-party Trace sources only)ConsoleRedirector(captures Console.Out → Trace for third-party libs; NUnit run is an exception — output.md)NotifyListenerLogLevelDetector(keyword-based level for bridged Trace messages)- File/HTTP targets and sink options
StartupTrace(pre-DI RAM buffer; writescrash_{app}_{ver}_{pid}.logon startup failure or unhandled exception while trace is active — not a ZLogger provider)
DevTools.Presentation owns the monitor pane (IMonitorLogTarget, MonitorLogTarget, AddMonitorLogging) and WPF PresentationTraceSources attach/detach.
Host projects own when listeners are registered, which enrichers are active, and any host-specific linkification or geometry routing. Hosts call AddLoggingProvider() then AddMonitorLogging.
flowchart LR
Shared["DevTools.Logging\nheadless ZLogger sinks/bridge"]
Pane["DevTools.Presentation\nmonitor + WPF trace"]
Revit["Revit LoggingService\nGeometryListener\nRevitLinkifier\nRevitContextProvider"]
Acad["Acad LoggingService\nAcadContextProvider"]
UI["DevTools.Presentation\nLogViewModel + settings UI"]
Revit --> Shared
Acad --> Shared
Revit --> Pane
Acad --> Pane
UI --> Shared
UI --> Pane
Revit registration is in RevitHostingExtensions.AddLoggingServices(). AutoCAD registration is in AcadHostingExtensions.AddLoggingServices().
LogLevelDetector scans bridged Trace message content and maps known keywords to MEL levels. Only applies to the LoggerTraceListener bridge path — ILogger<T> calls already carry explicit levels.
| Level | Example keywords |
|---|---|
| Critical | CRITICAL, FATAL, PANIC, SECURITY |
| Error | ERROR, FAILED, EXCEPTION, TIMEOUT, INVALID |
| Warning | WARNING, DEPRECATED, OBSOLETE, MEMORY, RETRY |
| Information | default |
Revit adds behavior that must not leak into shared logging:
GeometryListenerintercepts Revit geometry objects written toTrace.RevitLinkifier(Linkify) detects monitor tokens: optional{linkInstanceId}@scope plus ElementId / UniqueId / IfcGuid. Match and click only; search / select / box types live inRevitDevTool.Tools.Selection.RevitContextProviderenriches log records with selected Revit context fields.- Visualization routing sends geometry to DirectContext3D servers under
source/RevitDevTool/Visualization/.
Linked-element token, Element Finder, and Tools ownership: 0036.
AutoCAD has its own context provider/enricher path and does not share Revit geometry routing.
| Target | Implementation | Notes |
|---|---|---|
| Monitor | MonitorLogTarget (Presentation) |
UI monitor through Scintilla/ZLogger integration. |
| File | FileLogProcessor |
Rolling log_{app}_{ver}_{pid}_{timestamp}_{seq} when file logging is enabled. |
| Startup | StartupTrace |
Pre-DI. Buffer in RAM; create crash_{app}_{ver}_{pid}.log only if Fail runs (catch or unhandled while trace is active). Milestone lines use +seconds elapsed, not ZLogger [HH:mm:ss LVL]. AutoClean ignores crash_*. |
| HTTP | HttpLogProcessor |
Remote sink path. |
| Notify | NotifyListener |
UI update notifications (bridge path only). |
Third-party addins that want their log output to appear in the DevTools monitor and file sinks can bridge their own ILogger<T> + ZLogger pipeline into DevTools by routing log entries through System.Diagnostics.Trace.
Note: Depending on the addin's architecture, you can wire logging through Microsoft.Extensions.Hosting or directly via IServiceCollection. The example below uses the simplest ServiceCollection-only approach.
// In the third-party addin's service registration:
private static ServiceProvider CreateServiceProvider()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddZLoggerInMemory(
processorKey: null,
configure: options =>
{
options.UsePlainTextFormatter(formatter =>
{
formatter.SetPrefixFormatter(
$"[{0:short}] [{1}] ",
(in template, in info) =>
template.Format(info.LogLevel, info.Category));
});
},
configureProcessor: processor =>
{
processor.MessageReceived += msg =>
System.Diagnostics.Trace.WriteLine(msg);
});
});
return services.BuildServiceProvider();
}How it works:
- The third-party addin uses its own
ILogger<T>+ ZLogger with an in-memory processor. MessageReceivedwrites each formatted log entry toSystem.Diagnostics.Trace.WriteLine().- DevTools'
LoggerTraceListener(always active onceLoggingService.Initialize()runs) interceptsTraceoutput and routes it into the shared MEL pipeline. - The log entry flows through
LogLevelDetector→NotifyListener(auto-show pane) → ZLogger providers → Monitor / File / HTTP sinks.
Key points:
- The third-party addin does not need a dependency on DevTools libraries.
- Only
System.Diagnostics.TraceandZLoggerare required. - The
NotifyListener.TraceReceivedevent fires for every bridged entry, triggering auto-show of the DevTools dockable pane or floating window. - For addins that already use
Trace.Write, no ZLogger setup is needed —LoggerTraceListenercapturesTraceoutput directly. - The
[level]prefix in the formatter (${0:short}) is required:Trace.WriteLinedoes not carry level information, soLogLevelDetectorscans the message for level prefixes like[DBG],[ERR],[WRN]to determine the correct MEL log level.
As of 2026-06-28, all project-owned code uses ILogger<T> + ZLogger. The LoggerTraceListener bridge remains active for:
- WPF
PresentationTraceSources(data binding errors, etc.) - Revit SDK internal traces
- Python runtime
Trace.Write(stdout capture from embedded scripts) - Any third-party library that writes to
System.Diagnostics.Trace
Do not add new Trace.TraceError/Warning/Information or Debug.WriteLine calls in project-owned code. Use ILogger<T> injection instead.
- Use
ILogger<T>for all new logging in project-owned code. - Keep
LoggerTraceListenerbridge for third-party/WPF trace sources. - Keep shared logging host-neutral.
- Put host document/context/linkification/geometry behavior in host projects.
- If changing geometry routing, update
docs/architecture/Visualization/README.mdtoo. - If changing sinks or shared options, update this doc and
docs/agents/host-boundaries.mdwhen the boundary changes.
docs/architecture/Visualization/README.mddocs/architecture/Execution/README.mddocs/agents/host-boundaries.md