English | 简体中文
Ling.Interceptors is a Roslyn analyzer and incremental source generator for compile-time C# method-call interception and monitoring. It requires .NET SDK 9.0.200 or later.
| Package | Purpose |
|---|---|
Ling.Interceptors |
Public interception and monitoring API, bundled analyzer, generator, and build integration. |
Ling.Interceptors.Logging |
Microsoft.Extensions.Logging sink. |
Ling.Interceptors.Console |
JSON Lines sink for standard error. |
Ling.Interceptors.OpenTelemetry |
ActivitySource and Meter sink. |
dotnet add package Ling.InterceptorsThe package automatically adds Ling.Interceptors.Generated to InterceptorsNamespaces; consumer projects do not need to edit that property.
Monitoring runtime contracts, the no-op default sink, and the default value formatter are included in Ling.Interceptors; the main package has no System.Text.Json dependency.
Mark the target method instead of writing a replacement. The generator finds calls in the current compilation and emits the wrapper automatically:
[Monitor(CaptureParameters = true, CaptureReturnValue = true)]
[return: SensitiveData]
public async Task<Order> PlaceOrder(
int customerId,
[SensitiveData] string product)
{
// implementation
}SensitiveData is independent from Monitor. Sensitive strings are partially masked; other sensitive values are redacted before a sink receives them. Parameters and return values are opt-in, while exceptions and timing are enabled by default.
Configure an output sink explicitly. The default is a no-op:
MonitorRuntime.Sink = new LoggerMonitorSink(loggerFactory);
// or: new ConsoleMonitorSink()
// or: new OpenTelemetryMonitorSink()The default formatter preserves scalar values, masks sensitive values, and represents other objects as a declared-type summary without calling ToString() or serializing them. Set MonitorRuntime.Formatter to a custom formatter when structured values are required; MonitorValueContext.IsSensitive identifies values that must not be exposed.
Calls in a referenced assembly are not rewritten. Both the declaring project and a project that calls a monitored method need Ling.Interceptors.
Declare a static replacement method. The second constructor argument must use a qualified nameof(T.Method) expression, and the scope is required.
using Ling.Interceptors;
internal sealed class Service
{
internal void Send(string value) => Console.WriteLine($"original:{value}");
}
internal static class Replacements
{
[Intercept("trace", nameof(Service.Send),
InterceptionScope.Explicit | InterceptionScope.Compilation)]
internal static void Send(Service service, string value)
{
Console.WriteLine($"trace:{value}");
service.Send(value); // Calls the original implementation.
}
}Use an explicit marker for one call site, or let Compilation match ordinary calls throughout the current project:
service./* intercept:trace */Send("one"); // Explicit replacement
service.Send("two"); // Compilation replacement| Scope | Meaning |
|---|---|
Explicit |
Allows a call site to select the rule with /* intercept:id */. |
Compilation |
Replaces matching ordinary method calls in the current compilation. |
GeneratedCode |
Extends a Compilation rule to generated files and enables the two-phase build when other generators emit source. |
None |
Disables the rule. |
GeneratedCode must be combined with Compilation. Explicit rules have priority over compilation-wide rules. Calls inside a replacement method (including its lambdas and local functions) are excluded, so a replacement can safely call the original implementation.
- The replacement method must be internal or public, static, and signature-compatible with the target method.
- Targets may be in the current project or a referenced assembly, but already-compiled DLL internals cannot be changed.
- Constructors, properties, operators, delegate invocations, and method-group conversions are not supported.
- Rules and replacement methods are defined in the current compilation in v1.
- Monitoring supports ordinary methods,
Task,Task<T>,ValueTask, andValueTask<T>; ref-return methods are not monitored.
For Compilation | GeneratedCode, the package recognizes conventional generated files (<auto-generated>, .g.cs, .generated.cs, .designer.cs) and GeneratedCodeAttribute. During command-line Build, Pack, and Publish, it materializes output from other source generators and runs a final compilation so same-round generated calls can be intercepted. Design-time builds remain a preview and do not initiate nested compilation.
Each sample is a small executable application. They demonstrate integration choices rather than serving as test projects.
- Basic interception and monitoring — Starts with explicit and compilation-wide
[Intercept]rules, then shows synchronous,Task, andValueTask[Monitor]methods throughConsoleMonitorSink. Run:dotnet run --project samples/Ling.Interceptors.Sample - Package and two-phase build — Consumes the locally packed
Ling.InterceptorsNuGet package and demonstrates interception of a call emitted by another source generator. Run:dotnet run --project samples/Ling.Interceptors.PackSample ILoggerintegration — ConfiguresLoggerMonitorSinkwith a console logging provider and shows structured, masked method parameters. Run:dotnet run --project samples/Ling.Interceptors.LoggingSample- OpenTelemetry integration — Configures
OpenTelemetryMonitorSinkand uses nativeActivityListenerandMeterListenerto display emitted trace and duration data without choosing an SDK exporter. Run:dotnet run --project samples/Ling.Interceptors.OpenTelemetrySample - Cross-assembly monitoring — Keeps the
[Monitor]API in a referenced target assembly and installs the generator only in the caller, demonstrating the normal consumer topology. Run:dotnet run --project samples/Ling.Interceptors.CrossAssembly.Caller
- Build:
dotnet build Ling.Interceptors.slnx - Test .NET 9 and .NET 10:
dotnet test Ling.Interceptors.slnx - Pack:
dotnet pack src/Ling.Interceptors/Ling.Interceptors.csproj -c Release
Contributions are welcome. Please include xUnit coverage for behavior changes.