From b4f1e106d0ea536ca1ec64e20d5bed1caaddb878 Mon Sep 17 00:00:00 2001 From: Ryan Lettieri Date: Fri, 20 Oct 2023 21:25:57 -0600 Subject: [PATCH 1/6] Initial setup for workflow log tracing Signed-off-by: Ryan Lettieri --- src/Dapr.Workflow/WorkflowLoggingService.cs | 88 +++++++++++++++++++ src/Dapr.Workflow/WorkflowRuntimeOptions.cs | 4 + .../WorkflowServiceCollectionExtensions.cs | 2 +- 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/Dapr.Workflow/WorkflowLoggingService.cs diff --git a/src/Dapr.Workflow/WorkflowLoggingService.cs b/src/Dapr.Workflow/WorkflowLoggingService.cs new file mode 100644 index 000000000..a384bfcb3 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowLoggingService.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + + /// + /// Defines runtime options for workflows. + /// + internal sealed class WorkflowLoggingService : IHostedService + { + private readonly ILogger logger; + private static HashSet? registeredWorkflows; + private static HashSet? registeredActivities; + private LogLevel logLevel = LogLevel.Debug; + + public WorkflowLoggingService(ILogger logger) + { + var value = Environment.GetEnvironmentVariable("DAPR_LOG_LEVEL"); + logLevel = string.IsNullOrEmpty(value) ? LogLevel.Debug : (LogLevel)Enum.Parse(typeof(LogLevel), value); + this.logger = logger; + registeredActivities = new HashSet(); + registeredWorkflows = new HashSet(); + } + public Task StartAsync(CancellationToken cancellationToken) + { + this.logger.Log(logLevel, "WorkflowLoggingService started"); + + if (registeredWorkflows != null) + { + this.logger.Log(logLevel, "List of registered workflows"); + foreach (string item in registeredWorkflows) + { + this.logger.Log(logLevel, item); + } + } + + if (registeredActivities != null) + { + this.logger.Log(logLevel, "List of registered activities:"); + foreach (string item in registeredActivities) + { + this.logger.Log(logLevel, item); + } + } + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this.logger.Log(logLevel, "WorkflowLoggingService stopped"); + + return Task.CompletedTask; + } + + public static void LogWorkflowName(string workflowName) + { + if (registeredWorkflows != null) + { + registeredWorkflows.Add(workflowName); + } + } + + public static void LogActivityName(string activityName) + { + if (registeredActivities != null) + { + registeredActivities.Add(activityName); + } + } + } +} diff --git a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs index 4dd202b1a..adc925777 100644 --- a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs +++ b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs @@ -54,6 +54,7 @@ public void RegisterWorkflow(string name, Func(string name, Func(); return new OrchestratorWrapper(workflow); }); + WorkflowLoggingService.LogWorkflowName(name); }); } @@ -91,6 +93,7 @@ public void RegisterActivity(string name, Func() where TActivity : class, IWorkflowActi TActivity activity = ActivatorUtilities.CreateInstance(serviceProvider); return new ActivityWrapper(activity); }); + WorkflowLoggingService.LogActivityName(name); }); } diff --git a/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs b/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs index 50880ab24..ca514f221 100644 --- a/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs +++ b/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs @@ -47,7 +47,7 @@ public static IServiceCollection AddDaprWorkflow( #pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient serviceCollection.TryAddSingleton(); #pragma warning restore CS0618 // Type or member is obsolete - + serviceCollection.AddHostedService(); serviceCollection.TryAddSingleton(); serviceCollection.AddDaprClient(); serviceCollection.AddDaprWorkflowClient(); From 30962d75818aaf7c6ba8244a1e9acfaa5854c817 Mon Sep 17 00:00:00 2001 From: Ryan Lettieri Date: Thu, 2 Nov 2023 06:44:37 -0600 Subject: [PATCH 2/6] Created log sink for E2E tests using serilog Signed-off-by: Ryan Lettieri --- src/Dapr.Workflow/WorkflowLoggingService.cs | 52 ++++++++-------- .../Dapr.E2E.Test.App.csproj | 7 +++ test/Dapr.E2E.Test.App/Program.cs | 28 +++++---- test/Dapr.E2E.Test.App/Startup.cs | 14 +++++ test/Dapr.E2E.Test/Workflows/WorkflowTest.cs | 59 +++++++++++++++++-- 5 files changed, 116 insertions(+), 44 deletions(-) diff --git a/src/Dapr.Workflow/WorkflowLoggingService.cs b/src/Dapr.Workflow/WorkflowLoggingService.cs index a384bfcb3..17db8cf6b 100644 --- a/src/Dapr.Workflow/WorkflowLoggingService.cs +++ b/src/Dapr.Workflow/WorkflowLoggingService.cs @@ -19,6 +19,7 @@ namespace Dapr.Workflow using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Configuration; /// /// Defines runtime options for workflows. @@ -26,63 +27,60 @@ namespace Dapr.Workflow internal sealed class WorkflowLoggingService : IHostedService { private readonly ILogger logger; - private static HashSet? registeredWorkflows; - private static HashSet? registeredActivities; - private LogLevel logLevel = LogLevel.Debug; + private static readonly HashSet registeredWorkflows = new(); + private static readonly HashSet registeredActivities = new(); + private LogLevel logLevel = LogLevel.Information; - public WorkflowLoggingService(ILogger logger) + public WorkflowLoggingService(ILogger logger, IConfiguration configuration) { - var value = Environment.GetEnvironmentVariable("DAPR_LOG_LEVEL"); - logLevel = string.IsNullOrEmpty(value) ? LogLevel.Debug : (LogLevel)Enum.Parse(typeof(LogLevel), value); + logLevel = string.IsNullOrEmpty(configuration["DAPR_LOG_LEVEL"]) ? LogLevel.Information : ConvertLogLevel(configuration["DAPR_LOG_LEVEL"]); this.logger = logger; - registeredActivities = new HashSet(); - registeredWorkflows = new HashSet(); + } public Task StartAsync(CancellationToken cancellationToken) { - this.logger.Log(logLevel, "WorkflowLoggingService started"); - - if (registeredWorkflows != null) - { - this.logger.Log(logLevel, "List of registered workflows"); + this.logger.Log(LogLevel.Information, "WorkflowLoggingService started"); + + this.logger.Log(LogLevel.Information, "List of registered workflows"); foreach (string item in registeredWorkflows) { - this.logger.Log(logLevel, item); + this.logger.Log(LogLevel.Information, item); } - } - if (registeredActivities != null) - { - this.logger.Log(logLevel, "List of registered activities:"); + this.logger.Log(LogLevel.Information, "List of registered activities:"); foreach (string item in registeredActivities) { - this.logger.Log(logLevel, item); + this.logger.Log(LogLevel.Information, item); } - } + return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { - this.logger.Log(logLevel, "WorkflowLoggingService stopped"); + this.logger.Log(LogLevel.Information, "WorkflowLoggingService stopped"); return Task.CompletedTask; } public static void LogWorkflowName(string workflowName) { - if (registeredWorkflows != null) - { - registeredWorkflows.Add(workflowName); - } + registeredWorkflows.Add(workflowName); } public static void LogActivityName(string activityName) { - if (registeredActivities != null) + registeredActivities.Add(activityName); + } + + private LogLevel ConvertLogLevel(string ?daprLogLevel) + { + LogLevel logLevel = LogLevel.Information; + if (!string.IsNullOrEmpty(daprLogLevel)) { - registeredActivities.Add(activityName); + Enum.TryParse(daprLogLevel, out logLevel); } + return logLevel; } } } diff --git a/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj b/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj index 30d47e7c2..5c81557b8 100644 --- a/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj +++ b/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj @@ -10,4 +10,11 @@ + + + + + + + diff --git a/test/Dapr.E2E.Test.App/Program.cs b/test/Dapr.E2E.Test.App/Program.cs index e617558bf..5713129d5 100644 --- a/test/Dapr.E2E.Test.App/Program.cs +++ b/test/Dapr.E2E.Test.App/Program.cs @@ -1,23 +1,25 @@ -// ------------------------------------------------------------------------ -// Copyright 2021 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// Copyright 2021 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ namespace Dapr.E2E.Test { using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; + using Serilog; public class Program { public static void Main(string[] args) { + Log.Logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); CreateHostBuilder(args).Build().Run(); } @@ -26,6 +28,6 @@ public static IHostBuilder CreateHostBuilder(string[] args) => .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); - }); + }).UseSerilog(); } } diff --git a/test/Dapr.E2E.Test.App/Startup.cs b/test/Dapr.E2E.Test.App/Startup.cs index d1f291bf9..8207c5883 100644 --- a/test/Dapr.E2E.Test.App/Startup.cs +++ b/test/Dapr.E2E.Test.App/Startup.cs @@ -29,6 +29,8 @@ namespace Dapr.E2E.Test using Microsoft.Extensions.Hosting; using System.Threading.Tasks; using System; + using Microsoft.Extensions.Logging; + using Serilog; /// /// Startup class. @@ -61,6 +63,10 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication().AddDapr(); services.AddAuthorization(o => o.AddDapr()); services.AddControllers().AddDapr(); + services.AddLogging(builder => + { + builder.AddConsole(); + }); // Register a workflow and associated activity services.AddDaprWorkflow(options => { @@ -108,11 +114,19 @@ public void ConfigureServices(IServiceCollection services) /// Webhost environment. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + var logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); + + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddSerilog(logger); + }); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + app.UseSerilogRequestLogging(); + app.UseRouting(); app.UseAuthentication(); diff --git a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs index 971d04098..4ba561b99 100644 --- a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs +++ b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs @@ -11,25 +11,76 @@ // limitations under the License. // ------------------------------------------------------------------------ using System; +using System.IO; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Dapr.Client; using FluentAssertions; using Xunit; +using System.Linq; +using System.Diagnostics; namespace Dapr.E2E.Test { [Obsolete] public partial class E2ETests { + [Fact] + public async Task TestWorkflowLogging() + { + // This test starts the daprclient and searches through the logfile to ensure the + // workflow logger is correctly logging the registered workflow(s) and activity(s) + + Dictionary logStrings = new Dictionary(); + logStrings["PlaceOrder"] = false; + logStrings["ShipProduct"] = false; + var logFilePath = "../../../../../test/Dapr.E2E.Test.App/log.txt"; + var allLogsFound = false; + var timeout = 30; // 30s + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)); + using var daprClient = new DaprClientBuilder().UseGrpcEndpoint(this.GrpcEndpoint).UseHttpEndpoint(this.HttpEndpoint).Build(); + var health = await daprClient.CheckHealthAsync(); + health.Should().Be(true, "DaprClient is not healthy"); + + var searchTask = Task.Run(() => + { + using (StreamReader reader = new StreamReader(logFilePath)) + { + string line; + + while ((line = reader.ReadLine()) != null) + { + foreach (var entry in logStrings) + { + if (line.Contains(entry.Key)) + { + logStrings[entry.Key] = true; + } + } + allLogsFound = logStrings.All(k => k.Value); + if (allLogsFound) + { + break; + } + } + } + }, cts.Token); + + if (!Task.WaitAll(new Task[] {searchTask}, timeout)) + { + File.Delete(logFilePath); + Assert.True(false, "Expected logs weren't found within timeout period"); + } + File.Delete(logFilePath); + } [Fact] public async Task TestWorkflows() { - string instanceId = "testInstanceId"; - string instanceId2 = "EventRaiseId"; - string workflowComponent = "dapr"; - string workflowName = "PlaceOrder"; + var instanceId = "testInstanceId"; + var instanceId2 = "EventRaiseId"; + var workflowComponent = "dapr"; + var workflowName = "PlaceOrder"; object input = "paperclips"; Dictionary workflowOptions = new Dictionary(); workflowOptions.Add("task_queue", "testQueue"); From 5649d741067d579db5125157bce9587cfcf8090f Mon Sep 17 00:00:00 2001 From: Ryan Lettieri Date: Thu, 2 Nov 2023 07:07:46 -0600 Subject: [PATCH 3/6] Formatting Signed-off-by: Ryan Lettieri --- test/Dapr.E2E.Test/Workflows/WorkflowTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs index 4ba561b99..0badf67e9 100644 --- a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs +++ b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs @@ -35,7 +35,7 @@ public async Task TestWorkflowLogging() Dictionary logStrings = new Dictionary(); logStrings["PlaceOrder"] = false; logStrings["ShipProduct"] = false; - var logFilePath = "../../../../../test/Dapr.E2E.Test.App/log.txt"; + var logFilePath = "../../../../../test/Dapr.E2E.Test.App/log.txt"; var allLogsFound = false; var timeout = 30; // 30s using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)); From 7b3f3817613ef25fbe084631cfe089fb94327b7d Mon Sep 17 00:00:00 2001 From: Ryan Lettieri Date: Fri, 3 Nov 2023 04:44:41 -0600 Subject: [PATCH 4/6] Addressing feedback on review Signed-off-by: Ryan Lettieri --- src/Dapr.Workflow/WorkflowLoggingService.cs | 11 ----------- test/Dapr.E2E.Test.App/Program.cs | 1 - 2 files changed, 12 deletions(-) diff --git a/src/Dapr.Workflow/WorkflowLoggingService.cs b/src/Dapr.Workflow/WorkflowLoggingService.cs index 17db8cf6b..5297612c1 100644 --- a/src/Dapr.Workflow/WorkflowLoggingService.cs +++ b/src/Dapr.Workflow/WorkflowLoggingService.cs @@ -29,11 +29,9 @@ internal sealed class WorkflowLoggingService : IHostedService private readonly ILogger logger; private static readonly HashSet registeredWorkflows = new(); private static readonly HashSet registeredActivities = new(); - private LogLevel logLevel = LogLevel.Information; public WorkflowLoggingService(ILogger logger, IConfiguration configuration) { - logLevel = string.IsNullOrEmpty(configuration["DAPR_LOG_LEVEL"]) ? LogLevel.Information : ConvertLogLevel(configuration["DAPR_LOG_LEVEL"]); this.logger = logger; } @@ -73,14 +71,5 @@ public static void LogActivityName(string activityName) registeredActivities.Add(activityName); } - private LogLevel ConvertLogLevel(string ?daprLogLevel) - { - LogLevel logLevel = LogLevel.Information; - if (!string.IsNullOrEmpty(daprLogLevel)) - { - Enum.TryParse(daprLogLevel, out logLevel); - } - return logLevel; - } } } diff --git a/test/Dapr.E2E.Test.App/Program.cs b/test/Dapr.E2E.Test.App/Program.cs index 5713129d5..8e510d00f 100644 --- a/test/Dapr.E2E.Test.App/Program.cs +++ b/test/Dapr.E2E.Test.App/Program.cs @@ -19,7 +19,6 @@ public class Program { public static void Main(string[] args) { - Log.Logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); CreateHostBuilder(args).Build().Run(); } From 2f5f3f4d8217766cfb92d3020dae628076891a8b Mon Sep 17 00:00:00 2001 From: Ryan Lettieri Date: Sat, 4 Nov 2023 01:38:30 -0600 Subject: [PATCH 5/6] Addressing some review comments Signed-off-by: Ryan Lettieri --- src/Dapr.Workflow/WorkflowLoggingService.cs | 20 ++++++++++---------- test/Dapr.E2E.Test.App/Program.cs | 1 + test/Dapr.E2E.Test/Workflows/WorkflowTest.cs | 17 +++++++++++------ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Dapr.Workflow/WorkflowLoggingService.cs b/src/Dapr.Workflow/WorkflowLoggingService.cs index 5297612c1..482d95b97 100644 --- a/src/Dapr.Workflow/WorkflowLoggingService.cs +++ b/src/Dapr.Workflow/WorkflowLoggingService.cs @@ -39,17 +39,17 @@ public Task StartAsync(CancellationToken cancellationToken) { this.logger.Log(LogLevel.Information, "WorkflowLoggingService started"); - this.logger.Log(LogLevel.Information, "List of registered workflows"); - foreach (string item in registeredWorkflows) - { - this.logger.Log(LogLevel.Information, item); - } + this.logger.Log(LogLevel.Information, "List of registered workflows"); + foreach (string item in registeredWorkflows) + { + this.logger.Log(LogLevel.Information, item); + } - this.logger.Log(LogLevel.Information, "List of registered activities:"); - foreach (string item in registeredActivities) - { - this.logger.Log(LogLevel.Information, item); - } + this.logger.Log(LogLevel.Information, "List of registered activities:"); + foreach (string item in registeredActivities) + { + this.logger.Log(LogLevel.Information, item); + } return Task.CompletedTask; } diff --git a/test/Dapr.E2E.Test.App/Program.cs b/test/Dapr.E2E.Test.App/Program.cs index 8e510d00f..5713129d5 100644 --- a/test/Dapr.E2E.Test.App/Program.cs +++ b/test/Dapr.E2E.Test.App/Program.cs @@ -19,6 +19,7 @@ public class Program { public static void Main(string[] args) { + Log.Logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); CreateHostBuilder(args).Build().Run(); } diff --git a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs index 0badf67e9..b996e49d1 100644 --- a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs +++ b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs @@ -43,13 +43,12 @@ public async Task TestWorkflowLogging() var health = await daprClient.CheckHealthAsync(); health.Should().Be(true, "DaprClient is not healthy"); - var searchTask = Task.Run(() => + var searchTask = Task.Run(async() => { using (StreamReader reader = new StreamReader(logFilePath)) { string line; - - while ((line = reader.ReadLine()) != null) + while ((line = await reader.ReadLineAsync()) != null) { foreach (var entry in logStrings) { @@ -67,12 +66,18 @@ public async Task TestWorkflowLogging() } }, cts.Token); - if (!Task.WaitAll(new Task[] {searchTask}, timeout)) + try + { + await searchTask; + } + finally { File.Delete(logFilePath); - Assert.True(false, "Expected logs weren't found within timeout period"); + if (!allLogsFound) + { + Assert.True(false, "The logs were not able to found within the timeout"); + } } - File.Delete(logFilePath); } [Fact] public async Task TestWorkflows() From 3917fb9f1cb505a51ca04e3331ff045c4e7ee063 Mon Sep 17 00:00:00 2001 From: Ryan Lettieri Date: Sun, 5 Nov 2023 04:35:02 -0700 Subject: [PATCH 6/6] Addressing more feedbck in the workflow e2e test Signed-off-by: Ryan Lettieri --- test/Dapr.E2E.Test/Workflows/WorkflowTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs index b996e49d1..979c136da 100644 --- a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs +++ b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs @@ -48,7 +48,7 @@ public async Task TestWorkflowLogging() using (StreamReader reader = new StreamReader(logFilePath)) { string line; - while ((line = await reader.ReadLineAsync()) != null) + while ((line = await reader.ReadLineAsync().WaitAsync(cts.Token)) != null) { foreach (var entry in logStrings) { @@ -73,10 +73,10 @@ public async Task TestWorkflowLogging() finally { File.Delete(logFilePath); - if (!allLogsFound) - { - Assert.True(false, "The logs were not able to found within the timeout"); - } + } + if (!allLogsFound) + { + Assert.True(false, "The logs were not able to found within the timeout"); } } [Fact]