diff --git a/sandbox/GeneratorSandbox/GeneratorSandbox.csproj b/sandbox/GeneratorSandbox/GeneratorSandbox.csproj
index df0ca9fd..c0269e39 100644
--- a/sandbox/GeneratorSandbox/GeneratorSandbox.csproj
+++ b/sandbox/GeneratorSandbox/GeneratorSandbox.csproj
@@ -17,12 +17,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/sandbox/GeneratorSandbox/Program.cs b/sandbox/GeneratorSandbox/Program.cs
index 8b9af7a5..06182ab1 100644
--- a/sandbox/GeneratorSandbox/Program.cs
+++ b/sandbox/GeneratorSandbox/Program.cs
@@ -1,84 +1,122 @@
using ConsoleAppFramework;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+using System.Diagnostics;
-args = ["hello"];
+// Use SigNoz profiling: (view: http://localhost:8080/ )
+// git clone https://github.com/SigNoz/signoz.git
+// cd signoz/deploy/docker
+// docker compose up
+Environment.SetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"); // 4317 or 4318
-var builder = ConsoleApp.Create();
+var builder = Host.CreateApplicationBuilder(args);
-builder.UseFilter();
-builder.UseFilter();
+builder.Logging.AddOpenTelemetry(logging =>
+{
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+});
-builder.Add();
+builder.Services.AddOpenTelemetry()
+ .ConfigureResource(resource =>
+ {
+ resource.AddService("ConsoleAppFramework Telemetry Sample");
+ })
+ .WithMetrics(metrics =>
+ {
+ metrics.AddRuntimeInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddOtlpExporter();
+ })
+ .WithTracing(tracing =>
+ {
+ tracing.SetSampler(new AlwaysOnSampler())
+ .AddHttpClientInstrumentation()
+ .AddSource(ConsoleAppFrameworkSampleActivitySource.Name)
+ .AddOtlpExporter();
+ })
+ .WithLogging(logging =>
+ {
+ logging.AddOtlpExporter();
+ });
-await builder.RunAsync(args);
+var app = builder.ToConsoleAppBuilder();
-[ConsoleAppFilter]
-[ConsoleAppFilter]
-public class MyClass
-{
- [ConsoleAppFilter]
- [ConsoleAppFilter]
- public void Hello()
- {
- Console.Write("abcde");
- }
-}
+var consoleAppLoger = ConsoleApp.ServiceProvider.GetRequiredService>(); // already built service provider.
+ConsoleApp.Log = msg => consoleAppLoger.LogDebug(msg);
+ConsoleApp.LogError = msg => consoleAppLoger.LogError(msg);
-internal class NopFilter1(ConsoleAppFilter next)
- : ConsoleAppFilter(next)
-{
- public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
- {
- Console.Write(1);
- return Next.InvokeAsync(context, cancellationToken);
- }
-}
+app.UseFilter();
-internal class NopFilter2(ConsoleAppFilter next)
- : ConsoleAppFilter(next)
+app.Add("", async ([FromServices] ILogger logger/*, CancellationToken cancellationToken*/) =>
{
- public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
- {
- Console.Write(2);
- return Next.InvokeAsync(context, cancellationToken);
- }
-}
+ var cancellationToken = CancellationToken.None;
-internal class NopFilter3(ConsoleAppFilter next)
- : ConsoleAppFilter(next)
-{
- public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
- {
- Console.Write(3);
- return Next.InvokeAsync(context, cancellationToken);
- }
-}
+ using var httpClient = new HttpClient();
+ var ms = await httpClient.GetStringAsync("https://www.microsoft.com", cancellationToken);
+ logger.LogInformation(ms);
+ var google = await httpClient.GetStringAsync("https://www.google.com", cancellationToken);
+ logger.LogInformation(google);
-internal class NopFilter4(ConsoleAppFilter next)
- : ConsoleAppFilter(next)
-{
- public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
- {
- Console.Write(4);
- return Next.InvokeAsync(context, cancellationToken);
- }
-}
+ var ms2 = httpClient.GetStringAsync("https://www.microsoft.com", cancellationToken);
+ var google2 = httpClient.GetStringAsync("https://www.google.com", cancellationToken);
+ var apple2 = httpClient.GetStringAsync("https://www.apple.com", cancellationToken);
+ await Task.WhenAll(ms2, google2, apple2);
-internal class NopFilter5(ConsoleAppFilter next)
- : ConsoleAppFilter(next)
+ logger.LogInformation(apple2.Result);
+
+ logger.LogInformation("OK");
+});
+
+await app.RunAsync(args);
+
+public static class ConsoleAppFrameworkSampleActivitySource
{
- public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
- {
- Console.Write(5);
- return Next.InvokeAsync(context, cancellationToken);
- }
+ public const string Name = "ConsoleAppFrameworkSample";
+
+ public static ActivitySource Instance { get; } = new ActivitySource(Name);
}
-internal class NopFilter6(ConsoleAppFilter next)
- : ConsoleAppFilter(next)
+// Sample Activity filter
+internal class CommandTracingFilter(ConsoleAppFilter next) : ConsoleAppFilter(next)
{
- public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
+ public override async Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
{
- Console.Write(6);
- return Next.InvokeAsync(context, cancellationToken);
+ using var activity = ConsoleAppFrameworkSampleActivitySource.Instance.StartActivity("CommandStart");
+ if (activity != null)
+ {
+ activity.SetTag("console_app.command_name", context.CommandName);
+ activity.AddBaggage("console_app.command_args", string.Join(" ", context.EscapedArguments));
+ }
+
+ try
+ {
+ await Next.InvokeAsync(context, cancellationToken);
+ if (activity != null)
+ {
+ activity.SetStatus(ActivityStatusCode.Ok);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (activity != null)
+ {
+ if (ex is OperationCanceledException)
+ {
+ activity.SetStatus(ActivityStatusCode.Error, "Canceled");
+ }
+ else
+ {
+ activity.AddException(ex);
+ activity.SetStatus(ActivityStatusCode.Error);
+ }
+ }
+ throw;
+ }
}
}
diff --git a/sandbox/GeneratorSandbox/Properties/launchSettings.json b/sandbox/GeneratorSandbox/Properties/launchSettings.json
index 91cb9250..3ab90ae0 100644
--- a/sandbox/GeneratorSandbox/Properties/launchSettings.json
+++ b/sandbox/GeneratorSandbox/Properties/launchSettings.json
@@ -2,7 +2,7 @@
"profiles": {
"Default": {
"commandName": "Project",
- "commandLineArgs": "run --project foo.csproj -- --foo 100 --bar bazbaz"
+ "commandLineArgs": ""
},
"ShowVersion": {
"commandName": "Project",
diff --git a/src/ConsoleAppFramework/ConsoleAppBaseCode.cs b/src/ConsoleAppFramework/ConsoleAppBaseCode.cs
index 13698809..ae5e2258 100644
--- a/src/ConsoleAppFramework/ConsoleAppBaseCode.cs
+++ b/src/ConsoleAppFramework/ConsoleAppBaseCode.cs
@@ -371,9 +371,9 @@ static void ShowVersion()
static partial void ShowHelp(int helpId);
- static async Task RunWithFilterAsync(string commandName, string[] args, int commandDepth, int escapeIndex, ConsoleAppFilter invoker)
+ static async Task RunWithFilterAsync(string commandName, string[] args, int commandDepth, int escapeIndex, ConsoleAppFilter invoker, CancellationToken cancellationToken)
{
- using var posixSignalHandler = PosixSignalHandler.Register(Timeout);
+ using var posixSignalHandler = PosixSignalHandler.Register(Timeout, cancellationToken);
try
{
await Task.Run(() => invoker.InvokeAsync(new ConsoleAppContext(commandName, args, null, commandDepth, escapeIndex), posixSignalHandler.Token)).WaitAsync(posixSignalHandler.TimeoutToken);
@@ -411,16 +411,16 @@ sealed class PosixSignalHandler : IDisposable
PosixSignalRegistration? sigQuit;
PosixSignalRegistration? sigTerm;
- PosixSignalHandler(TimeSpan timeout)
+ PosixSignalHandler(TimeSpan timeout, CancellationToken cancellationToken)
{
- this.cancellationTokenSource = new CancellationTokenSource();
+ this.cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
this.timeoutCancellationTokenSource = new CancellationTokenSource();
this.timeout = timeout;
}
- public static PosixSignalHandler Register(TimeSpan timeout)
+ public static PosixSignalHandler Register(TimeSpan timeout, CancellationToken cancellationToken)
{
- var handler = new PosixSignalHandler(timeout);
+ var handler = new PosixSignalHandler(timeout, cancellationToken);
Action handleSignal = handler.HandlePosixSignal;
@@ -443,6 +443,7 @@ public void Dispose()
sigInt?.Dispose();
sigQuit?.Dispose();
sigTerm?.Dispose();
+ cancellationTokenSource.Dispose();
timeoutCancellationTokenSource.Dispose();
}
}
@@ -482,10 +483,10 @@ public void UseFilter() where T : ConsoleAppFilter { }
partial void AddCore(string commandName, Delegate command);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- partial void RunCore(string[] args);
+ partial void RunCore(string[] args, CancellationToken cancellationToken);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- partial void RunAsyncCore(string[] args, ref Task result);
+ partial void RunAsyncCore(string[] args, CancellationToken cancellationToken, ref Task result);
partial void BuildAndSetServiceProvider();
@@ -546,15 +547,14 @@ internal static partial class ConsoleApp
internal partial class ConsoleAppBuilder
{
public void Run(string[] args) => Run(args, true);
+ public void Run(string[] args, CancellationToken cancellationToken) => Run(args, true, cancellationToken);
- public Task RunAsync(string[] args) => RunAsync(args, true);
-
- public void Run(string[] args, bool disposeService)
+ public void Run(string[] args, bool disposeService, CancellationToken cancellationToken = default)
{
BuildAndSetServiceProvider();
try
{
- RunCore(args);
+ RunCore(args, cancellationToken);
}
finally
{
@@ -568,13 +568,16 @@ public void Run(string[] args, bool disposeService)
}
}
- public async Task RunAsync(string[] args, bool disposeService)
+ public Task RunAsync(string[] args) => RunAsync(args, true);
+ public Task RunAsync(string[] args, CancellationToken cancellationToken) => RunAsync(args, true, cancellationToken);
+
+ public async Task RunAsync(string[] args, bool disposeService, CancellationToken cancellationToken = default)
{
BuildAndSetServiceProvider();
try
{
Task? task = null;
- RunAsyncCore(args, ref task!);
+ RunAsyncCore(args, cancellationToken, ref task!);
if (task != null)
{
await task;
@@ -621,8 +624,9 @@ internal static partial class ConsoleApp
internal partial class ConsoleAppBuilder
{
public void Run(string[] args) => Run(args, true, true, true);
+ public void Run(string[] args, CancellationToken cancellationToken) => Run(args, true, true, true, cancellationToken);
- public void Run(string[] args, bool startHost, bool stopHost, bool disposeService)
+ public void Run(string[] args, bool startHost, bool stopHost, bool disposeService, CancellationToken cancellationToken = default)
{
BuildAndSetServiceProvider();
Microsoft.Extensions.Hosting.IHost? host = ConsoleApp.ServiceProvider?.GetService(typeof(Microsoft.Extensions.Hosting.IHost)) as Microsoft.Extensions.Hosting.IHost;
@@ -632,7 +636,7 @@ public void Run(string[] args, bool startHost, bool stopHost, bool disposeServic
{
host?.StartAsync().GetAwaiter().GetResult();
}
- RunCore(args);
+ RunCore(args, cancellationToken);
}
finally
{
@@ -651,8 +655,9 @@ public void Run(string[] args, bool startHost, bool stopHost, bool disposeServic
}
public Task RunAsync(string[] args) => RunAsync(args, true, true, true);
+ public Task RunAsync(string[] args, CancellationToken cancellationToken) => RunAsync(args, true, true, true, cancellationToken);
- public async Task RunAsync(string[] args, bool startHost, bool stopHost, bool disposeService)
+ public async Task RunAsync(string[] args, bool startHost, bool stopHost, bool disposeService, CancellationToken cancellationToken = default)
{
BuildAndSetServiceProvider();
Microsoft.Extensions.Hosting.IHost? host = ConsoleApp.ServiceProvider?.GetService(typeof(Microsoft.Extensions.Hosting.IHost)) as Microsoft.Extensions.Hosting.IHost;
@@ -663,7 +668,7 @@ public async Task RunAsync(string[] args, bool startHost, bool stopHost, bool di
await host?.StartAsync();
}
Task? task = null;
- RunAsyncCore(args, ref task!);
+ RunAsyncCore(args, cancellationToken, ref task!);
if (task != null)
{
await task;
diff --git a/src/ConsoleAppFramework/Emitter.cs b/src/ConsoleAppFramework/Emitter.cs
index 21cc004d..3167a7a8 100644
--- a/src/ConsoleAppFramework/Emitter.cs
+++ b/src/ConsoleAppFramework/Emitter.cs
@@ -42,7 +42,9 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy
}
var commandDepthEscapeIndex = emitForBuilder ? ", int commandDepth, int escapeIndex" : "";
- var filterCancellationToken = command.HasFilter ? ", ConsoleAppContext context, CancellationToken cancellationToken" : "";
+ var filterCancellationToken = command.HasFilter ? ", ConsoleAppContext context, CancellationToken cancellationToken"
+ : emitForBuilder ? ", CancellationToken __ExternalCancellationToken__"
+ : "";
if (!emitForBuilder)
{
@@ -83,7 +85,7 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy
// prepare argument variables
if (hasCancellationToken)
{
- sb.AppendLine("using var posixSignalHandler = PosixSignalHandler.Register(Timeout);");
+ sb.AppendLine("using var posixSignalHandler = PosixSignalHandler.Register(Timeout, __ExternalCancellationToken__);");
}
if (hasConsoleAppContext)
{
@@ -412,7 +414,7 @@ public void EmitBuilder(SourceBuilder sb, CommandWithId[] commandIds, bool emitS
if (emitSync)
{
sb.AppendLine();
- using (sb.BeginBlock("partial void RunCore(string[] args)"))
+ using (sb.BeginBlock("partial void RunCore(string[] args, CancellationToken cancellationToken)"))
{
if (hasRootCommand)
{
@@ -431,7 +433,7 @@ public void EmitBuilder(SourceBuilder sb, CommandWithId[] commandIds, bool emitS
if (emitAsync)
{
sb.AppendLine();
- using (sb.BeginBlock("partial void RunAsyncCore(string[] args, ref Task result)"))
+ using (sb.BeginBlock("partial void RunAsyncCore(string[] args, CancellationToken cancellationToken, ref Task result)"))
{
if (hasRootCommand)
{
@@ -559,16 +561,16 @@ void EmitLeafCommand(CommandWithId? command)
{
if (!isRunAsync)
{
- sb.AppendLine($"RunCommand{command.Id}(args, {depth}, args.AsSpan().IndexOf(\"--\"){commandArgs});");
+ sb.AppendLine($"RunCommand{command.Id}(args, {depth}, args.AsSpan().IndexOf(\"--\"){commandArgs}, cancellationToken);");
}
else
{
- sb.AppendLine($"result = RunCommand{command.Id}Async(args, {depth}, args.AsSpan().IndexOf(\"--\"){commandArgs});");
+ sb.AppendLine($"result = RunCommand{command.Id}Async(args, {depth}, args.AsSpan().IndexOf(\"--\"){commandArgs}, cancellationToken);");
}
}
else
{
- var invokeCode = $"RunWithFilterAsync(\"{command.Command.Name}\", args, {depth}, args.AsSpan().IndexOf(\"--\"), new Command{command.Id}Invoker({commandArgs.TrimStart(',', ' ')}).BuildFilter())";
+ var invokeCode = $"RunWithFilterAsync(\"{command.Command.Name}\", args, {depth}, args.AsSpan().IndexOf(\"--\"), new Command{command.Id}Invoker({commandArgs.TrimStart(',', ' ')}).BuildFilter(), cancellationToken)";
if (!isRunAsync)
{
sb.AppendLine($"{invokeCode}.GetAwaiter().GetResult();");