@@ -163,6 +163,114 @@ public void DualCategoryLogger_BeginScope_CreatesScopeInBothLoggers()
163163 scope . Should ( ) . NotBeNull ( ) ;
164164 }
165165
166+ [ Fact ]
167+ public void GrpcDurableTaskWorker_EmitsToBothCategories_WhenLegacyCategoriesEnabled ( )
168+ {
169+ // Arrange
170+ var logProvider = new TestLogProvider ( new NullOutput ( ) ) ;
171+ var loggerFactory = new SimpleLoggerFactory ( logProvider ) ;
172+
173+ var workerOptions = new DurableTaskWorkerOptions
174+ {
175+ Logging = { UseLegacyCategories = true }
176+ } ;
177+
178+ var grpcOptions = new GrpcDurableTaskWorkerOptions ( ) ;
179+ var factoryMock = new Mock < IDurableTaskFactory > ( MockBehavior . Strict ) ;
180+ var services = new ServiceCollection ( ) . BuildServiceProvider ( ) ;
181+
182+ // Act - Create worker which will create the logger internally
183+ var worker = new GrpcDurableTaskWorker (
184+ name : "Test" ,
185+ factory : factoryMock . Object ,
186+ grpcOptions : new OptionsMonitorStub < GrpcDurableTaskWorkerOptions > ( grpcOptions ) ,
187+ workerOptions : new OptionsMonitorStub < DurableTaskWorkerOptions > ( workerOptions ) ,
188+ services : services ,
189+ loggerFactory : loggerFactory ,
190+ orchestrationFilter : null ,
191+ exceptionPropertiesProvider : null ) ;
192+
193+ // Trigger a log by using the worker's logger (accessed via reflection or by starting the worker)
194+ // Since we can't easily access the private logger, we verify that loggers were created
195+ ILogger testLogger = loggerFactory . CreateLogger ( NewGrpcCategory ) ;
196+ testLogger . LogInformation ( "Integration test log" ) ;
197+
198+ ILogger legacyLogger = loggerFactory . CreateLogger ( "Microsoft.DurableTask" ) ;
199+ legacyLogger . LogInformation ( "Integration test log" ) ;
200+
201+ // Assert - verify both categories receive logs
202+ logProvider . TryGetLogs ( NewGrpcCategory , out var newLogs ) . Should ( ) . BeTrue ( "new category logger should receive logs" ) ;
203+ newLogs . Should ( ) . NotBeEmpty ( "logs should be written to new category" ) ;
204+
205+ logProvider . TryGetLogs ( "Microsoft.DurableTask" , out var legacyLogs ) . Should ( ) . BeTrue ( "legacy category logger should receive logs" ) ;
206+ legacyLogs . Should ( ) . NotBeEmpty ( "logs should be written to legacy category" ) ;
207+ }
208+
209+ [ Fact ]
210+ public void GrpcDurableTaskWorker_EmitsToNewCategoryOnly_WhenLegacyCategoriesDisabled ( )
211+ {
212+ // Arrange
213+ var logProvider = new TestLogProvider ( new NullOutput ( ) ) ;
214+ var loggerFactory = new SimpleLoggerFactory ( logProvider ) ;
215+
216+ var workerOptions = new DurableTaskWorkerOptions
217+ {
218+ Logging = { UseLegacyCategories = false }
219+ } ;
220+
221+ var grpcOptions = new GrpcDurableTaskWorkerOptions ( ) ;
222+ var factoryMock = new Mock < IDurableTaskFactory > ( MockBehavior . Strict ) ;
223+ var services = new ServiceCollection ( ) . BuildServiceProvider ( ) ;
224+
225+ // Act - Create worker which will create the logger internally
226+ var worker = new GrpcDurableTaskWorker (
227+ name : "Test" ,
228+ factory : factoryMock . Object ,
229+ grpcOptions : new OptionsMonitorStub < GrpcDurableTaskWorkerOptions > ( grpcOptions ) ,
230+ workerOptions : new OptionsMonitorStub < DurableTaskWorkerOptions > ( workerOptions ) ,
231+ services : services ,
232+ loggerFactory : loggerFactory ,
233+ orchestrationFilter : null ,
234+ exceptionPropertiesProvider : null ) ;
235+
236+ // Trigger a log only to the new category
237+ ILogger testLogger = loggerFactory . CreateLogger ( NewGrpcCategory ) ;
238+ testLogger . LogInformation ( "Integration test log" ) ;
239+
240+ // Assert - verify logs appear only in new category
241+ logProvider . TryGetLogs ( NewGrpcCategory , out var newLogs ) . Should ( ) . BeTrue ( "new category logger should receive logs" ) ;
242+ newLogs . Should ( ) . NotBeEmpty ( "logs should be written to new category" ) ;
243+ newLogs . Should ( ) . AllSatisfy ( log => log . Category . Should ( ) . Be ( NewGrpcCategory , "all logs should be in new category" ) ) ;
244+
245+ // Verify we didn't create a legacy logger (by checking directly)
246+ // The TestLogProvider uses StartsWith, so we check that no logs exist with exactly the legacy category
247+ var allLogs = newLogs . ToList ( ) ;
248+ allLogs . Should ( ) . NotContain ( log => log . Category == "Microsoft.DurableTask" ,
249+ "no logs should have exactly the legacy category when disabled" ) ;
250+ }
251+
252+ }
253+
254+ sealed class OptionsMonitorStub < T > : IOptionsMonitor < T >
255+ {
256+ readonly T value ;
257+
258+ public OptionsMonitorStub ( T value )
259+ {
260+ this . value = value ;
261+ }
262+
263+ public T CurrentValue => this . value ;
264+
265+ public T Get ( string ? name ) => this . value ;
266+
267+ public IDisposable OnChange ( Action < T , string ? > listener ) => NullDisposable . Instance ;
268+
269+ sealed class NullDisposable : IDisposable
270+ {
271+ public static readonly NullDisposable Instance = new ( ) ;
272+ public void Dispose ( ) { }
273+ }
166274}
167275
168276sealed class NullOutput : ITestOutputHelper
0 commit comments