Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a2bd575
init
Jan 13, 2026
c4634e2
dont print indents without --tree-logging paramter
Jan 13, 2026
b4496f3
removed name from scope, added usings
Jan 13, 2026
500b67d
working log-levels
Jan 16, 2026
7517a57
refactor
Jan 16, 2026
826e12c
fixes
Jan 16, 2026
3a5a945
removed reqeusts json
Feb 9, 2026
0b3a446
stack to immutable
Feb 9, 2026
9ad0e47
public to internal
Feb 20, 2026
18b40d9
simplified TreeScopeStateStore
Feb 20, 2026
de40d74
refactor TreeConsoleSink.Emit
Feb 25, 2026
66f1cf3
closing bracket level matches opening bracket lvl
Feb 25, 2026
5d12642
opening/closing bracket refactor
Feb 25, 2026
c2f0ed2
refactored scopes counting
Feb 25, 2026
e67b7be
allocating serilgo parser once
Feb 25, 2026
dc8e718
log template
Feb 25, 2026
0997265
coderabbitai recommended changes
Feb 25, 2026
cda0b87
removed redundant BuildIndentPrefix
Feb 25, 2026
63b8092
removed TreeScopeStateStore.cs, updated TreeScope.cs
Feb 25, 2026
ebb8155
resolved merge conflicts
Mar 4, 2026
2c93d3c
refactored request logging in ExecuteRequestStep
Mar 4, 2026
d93ed9f
AppendLine to Append for last line of print
Mar 11, 2026
47b9297
enhanced log formatting
Mar 11, 2026
c584002
refactor logging methods for improved clarity and consistency
Mar 11, 2026
7fac23e
added structured logging for pipeline steps and test case collections
Mar 11, 2026
820c905
added colors to tree logging
Mar 13, 2026
2f4b346
refactor logging implementation for improved clarity and structure
Mar 13, 2026
6e3b2d1
moved to new folder
Mar 13, 2026
e7fe799
remove unused usings
Mar 13, 2026
93f4b6c
removed redundant allocations and tolist() calls
Mar 19, 2026
db26c72
Merge branch 'master' into feature/visual-logs
Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/TeaPie.DotnetTool/LoggingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ internal class LoggingSettings : CommandSettings
[DefaultValue(false)]
[Description("Runs command silently, without displaying any output. Default: false.")]
public bool IsQuiet { get; init; }

[CommandOption("--tree-logging")]
Comment thread
mchlkntrv marked this conversation as resolved.
[DefaultValue(false)]
[Description("Uses tree-structured console output for logs. Default: false.")]
public bool UseTreeLogging { get; init; }
}
2 changes: 1 addition & 1 deletion src/TeaPie.DotnetTool/TestCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ protected override ApplicationBuilder ConfigureApplication(Settings settings)
appBuilder
.WithPath(path)
.WithTemporaryPath(settings.TemporaryPath ?? string.Empty)
.WithLogging(logLevel, pathToLogFile, settings.LogFileLogLevel, pathToRequestsLogFile)
.WithLogging(logLevel, pathToLogFile, settings.LogFileLogLevel, pathToRequestsLogFile, settings.UseTreeLogging)
.WithEnvironment(settings.Environment ?? string.Empty)
.WithEnvironmentFile(PathResolver.Resolve(settings.EnvironmentFilePath, string.Empty))
.WithReportFile(PathResolver.Resolve(settings.ReportFilePath, string.Empty))
Expand Down
12 changes: 10 additions & 2 deletions src/TeaPie/ApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public sealed class ApplicationBuilder
private string _pathToRequestsLogFile = string.Empty;

private bool _variablesCaching = true;
private bool _useTreeLogging = false;

private Func<IServiceProvider, IPipelineStep[]> _pipelineBuildFunction = ApplicationStepsFactory.CreateDefaultPipelineSteps;

Expand Down Expand Up @@ -62,12 +63,14 @@ public ApplicationBuilder WithLogging(
LogLevel minimumLevel,
string pathToLogFile = "",
LogLevel minimumLevelForLogFile = LogLevel.None,
string pathToRequestsLogFile = "")
string pathToRequestsLogFile = "",
bool useTreeLogging = false)
{
_minimumLogLevel = minimumLevel;
_pathToLogFile = pathToLogFile;
_minimumLevelForLogFile = minimumLevelForLogFile;
_pathToRequestsLogFile = pathToRequestsLogFile;
_useTreeLogging = useTreeLogging;
return this;
}

Expand Down Expand Up @@ -163,7 +166,12 @@ private ApplicationContext GetApplicationContext(IServiceProvider provider)
private void ConfigureServices()
=> _services.AddTeaPie(
_isCollectionRun,
() => _services.ConfigureLogging(_minimumLogLevel, _pathToLogFile, _minimumLevelForLogFile, _pathToRequestsLogFile));
() => _services.ConfigureLogging(
_minimumLogLevel,
_pathToLogFile,
_minimumLevelForLogFile,
_pathToRequestsLogFile,
_useTreeLogging));

private static TeaPie CreateUserContext(IServiceProvider provider, ApplicationContext applicationContext)
=> TeaPie.Create(
Expand Down
19 changes: 18 additions & 1 deletion src/TeaPie/ApplicationStepsFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ο»Ώusing TeaPie.Environments;
using TeaPie.Logging.Tree;
using TeaPie.Pipelines;
using TeaPie.Reporting;
using TeaPie.Scripts;
Expand All @@ -11,15 +12,31 @@ namespace TeaPie;
internal static class ApplicationStepsFactory
{
public static IPipelineStep[] CreateDefaultPipelineSteps(IServiceProvider provider)
=> [provider.GetStep<ResolvePathsStep>(),
{
IDisposable? initScope = null;

return [
new InlineStep((context, _) =>
{
initScope = context.Logger.BeginOuterTreeScope();
return Task.CompletedTask;
}),
provider.GetStep<ResolvePathsStep>(),
provider.GetStep<ExploreStructureStep>(),
provider.GetStep<TryLoadVariablesStep>(),
provider.GetStep<InitializeEnvironmentsStep>(),
provider.GetStep<InitializeApplicationStep>(),
new InlineStep((_, _) =>
{
initScope?.Dispose();
initScope = null;
return Task.CompletedTask;
}),
provider.GetStep<GenerateStepsForTestCasesStep>(),
provider.GetStep<ReportTestResultsSummaryStep>(),
provider.GetStep<SaveVariablesStep>()
];
}

public static IPipelineStep[] CreateStructureExplorationSteps(IServiceProvider provider)
=> [provider.GetStep<ResolvePathsStep>(),
Expand Down
12 changes: 8 additions & 4 deletions src/TeaPie/Http/Auth/OAuth2/OAuth2Provider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text.Json.Serialization;
using TeaPie.Http.Headers;
using TeaPie.Variables;
using TeaPie.Logging.Tree;

namespace TeaPie.Http.Auth.OAuth2;

Expand Down Expand Up @@ -63,13 +64,16 @@ private async Task<string> GetTokenFromRequest()
{
ResolveParameters(out var requestContent, out var requestUri);

LogSendingRequest();
using (_logger.BeginTreeScope())
{
LogSendingRequest();

var result = await SendRequest(requestContent, requestUri);
var result = await SendRequest(requestContent, requestUri);
Comment thread
mchlkntrv marked this conversation as resolved.

CacheToken(result);
CacheToken(result);

return result.AccessToken!;
return result.AccessToken!;
}
}

private void LogSendingRequest()
Expand Down
28 changes: 21 additions & 7 deletions src/TeaPie/Http/ExecuteRequestStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Polly;
using TeaPie.Http.Auth;
using TeaPie.Http.Headers;
using TeaPie.Logging.Tree;
using TeaPie.Pipelines;
using TeaPie.Testing;

Expand Down Expand Up @@ -53,6 +54,7 @@ private void InsertStepForScheduledTestsIfAny(ApplicationContext context)
if (_testScheduler.HasScheduledTest())
{
_pipeline.InsertSteps(this, context.ServiceProvider.GetStep<ExecuteScheduledTestsStep>());

context.Logger.LogDebug("Tests from test directives were scheduled for execution.");
}
}
Expand All @@ -67,11 +69,14 @@ private async Task<HttpResponseMessage> ExecuteRequest(
ResolveAuthProvider(requestExecutionContext);

var client = _clientFactory.CreateClient(nameof(ExecuteRequestStep));
var response = await ExecuteRequest(
requestExecutionContext, resiliencePipeline, request, client, context.Logger, cancellationToken);
using (context.Logger.BeginTreeScope())
{
var response = await ExecuteRequest(
requestExecutionContext, resiliencePipeline, request, client, context.Logger, cancellationToken);

_authProviderAccessor.SetCurrentProviderToDefault();
return response;
_authProviderAccessor.SetCurrentProviderToDefault();
return response;
}
}

private void ResolveAuthProvider(RequestExecutionContext requestExecutionContext)
Expand Down Expand Up @@ -106,9 +111,18 @@ private async Task<HttpResponseMessage> ExecuteRequest(
return await resiliencePipeline.ExecuteAsync(async token =>
{
retryAttemptNumber = UpdateRetryAttemptNumber(logger, retryAttemptNumber);
var request = GetMessage(requestExecutionContext, originalMessage, content, ref messageUsed);
request.Options.Set(_contextKey, requestExecutionContext);
return await client.SendAsync(request, token);
var requestToSend = GetMessage(requestExecutionContext, originalMessage, content, ref messageUsed);
requestToSend.Options.Set(_contextKey, requestExecutionContext);

if (retryAttemptNumber > 0)
{
using (logger.BeginTreeScope())
Comment thread
mchlkntrv marked this conversation as resolved.
{
return await client.SendAsync(requestToSend, token);
}
}

return await client.SendAsync(requestToSend, token);
}, cancellationToken);
}

Expand Down
14 changes: 9 additions & 5 deletions src/TeaPie/Http/ParseHttpRequestStep.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ο»Ώusing Microsoft.Extensions.Logging;
using TeaPie.Http.Parsing;
using TeaPie.Logging;
using TeaPie.Logging.Tree;
using TeaPie.Pipelines;
using Timer = TeaPie.Logging.Timer;

Expand All @@ -26,11 +27,14 @@ public async Task Execute(ApplicationContext context, CancellationToken cancella

private void Parse(ApplicationContext context, RequestExecutionContext requestExecutionContext)
{
LogParsingStart(context, requestExecutionContext);

Timer.Execute(
() => _parser.Parse(requestExecutionContext),
elapsedTime => LogEndOfParsing(context, requestExecutionContext, elapsedTime));
using (context.Logger.BeginTreeScope())
{
LogParsingStart(context, requestExecutionContext);

Timer.Execute(
() => _parser.Parse(requestExecutionContext),
elapsedTime => LogEndOfParsing(context, requestExecutionContext, elapsedTime));
}
}

private static void LogParsingStart(ApplicationContext context, RequestExecutionContext requestExecutionContext)
Expand Down
2 changes: 1 addition & 1 deletion src/TeaPie/Http/Retrying/ResiliencePipelineProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ private void LogUsageOfRetryStrategy(string nameOfFinalStrategy, RetryStrategy f
sb.AppendLine($"Backoff type: '{finalRetryStrategy.BackoffType.ToString()}'");
sb.AppendLine($"Delay: {finalRetryStrategy.Delay.ToString()}");
sb.AppendLine($"Maximal delay: {finalRetryStrategy.MaxDelay?.ToString()}");
sb.AppendLine($"Use jitter: {finalRetryStrategy.UseJitter}");
sb.Append($"Use jitter: {finalRetryStrategy.UseJitter}");

return sb.ToString();
}
Expand Down
1 change: 1 addition & 0 deletions src/TeaPie/Logging/LoggingInterceptorHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ο»Ώusing Microsoft.Extensions.Logging;

namespace TeaPie.Logging;

internal class LoggingInterceptorHandler(ILogger<LoggingInterceptorHandler> logger) : DelegatingHandler
Expand Down
25 changes: 22 additions & 3 deletions src/TeaPie/Logging/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Serilog.Events;
using Serilog.Filters;
using Serilog.Formatting.Json;
using TeaPie.Logging.Tree;

namespace TeaPie.Logging;

Expand All @@ -26,8 +27,10 @@ public static IServiceCollection ConfigureLogging(
LogLevel minimumLevel,
string pathToLogFile = "",
LogLevel minimumLevelForLogFile = LogLevel.Debug,
string pathToRequestsLogFile = "")
string pathToRequestsLogFile = "",
bool useTreeLogging = false)
{
TreeLoggingExtensions.SetTreeLoggingEnabled(useTreeLogging);
if (minimumLevel == LogLevel.None)
{
Log.Logger = Serilog.Core.Logger.None;
Expand All @@ -37,9 +40,17 @@ public static IServiceCollection ConfigureLogging(
var config = new LoggerConfiguration()
.MinimumLevel.Is(GetMaximumFromMinimalLevels(minimumLevel, minimumLevelForLogFile))
.MinimumLevel.Override("System.Net.Http", ApplyRestrictiveLogLevelRule(minimumLevel))
.MinimumLevel.Override("TeaPie.Logging.NuGetLoggerAdapter", ApplyRestrictiveLogLevelRule(minimumLevel));
.MinimumLevel.Override("TeaPie.Logging.NuGetLoggerAdapter", ApplyRestrictiveLogLevelRule(minimumLevel))
.Enrich.FromLogContext();

AddConsoleSink(config, minimumLevel);
if (useTreeLogging)
Comment thread
mchlkntrv marked this conversation as resolved.
{
AddTreeConsoleSink(config, minimumLevel);
}
else
{
AddConsoleSink(config, minimumLevel);
}

if (!pathToLogFile.Equals(string.Empty) && minimumLevelForLogFile < LogLevel.None)
{
Expand Down Expand Up @@ -72,6 +83,14 @@ private static void AddConsoleSink(LoggerConfiguration config, LogLevel minimumL
.WriteTo.Console(restrictedToMinimumLevel: minimumLevel.ToSerilogLogLevel()));
}

private static void AddTreeConsoleSink(LoggerConfiguration config, LogLevel minimumLevel)
{
config
.WriteTo.Logger(lc => lc
.Filter.ByExcluding(Matching.FromSource("HttpRequests"))
.WriteTo.TreeConsole(restrictedToMinimumLevel: minimumLevel.ToSerilogLogLevel()));
}

private static void AddLogFileSink(LoggerConfiguration config, string pathToLogFile, LogLevel minimumLevelForLogFile)
{
config.WriteTo.Logger(lc => lc
Expand Down
118 changes: 118 additions & 0 deletions src/TeaPie/Logging/Tree/TreeConsoleFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using Serilog.Events;

namespace TeaPie.Logging.Tree;

internal static class TreeConsoleFormatter
{
private static readonly ConsoleColor[] _treeColors = [
ConsoleColor.Gray,
ConsoleColor.DarkGray,
ConsoleColor.Cyan,
ConsoleColor.Yellow,
ConsoleColor.DarkGray,
ConsoleColor.DarkGray
];

private static readonly Dictionary<Func<string, bool>, string> _symbolMappings = new()
{
{ msg => msg.StartsWith("Test Passed:") || msg.StartsWith("Test passed during retry"), "βœ” " },
{ msg => msg.StartsWith("Test '") && msg.Contains("failed"), "✘ " },
{ msg => msg.StartsWith("Skipping test:") || (msg.StartsWith("Test '") && msg.Contains("already executed")), "⏭ " },
{ msg => msg.StartsWith("Reason:"), " " }
};

internal static string PrependSymbol(string message)
{
foreach (var (predicate, symbol) in _symbolMappings)
{
if (predicate(message))
{
return symbol + message;
}
}

return message;
}

internal static void WriteMessageWithSymbolColor(string message)
{
if (string.IsNullOrEmpty(message))
{
Console.Out.WriteLine();
return;
}

switch (message[0])
{
case 'βœ”':
WriteColorText("βœ”", ConsoleColor.Green);
Console.Out.WriteLine(message[1..]);
break;
case '✘':
WriteColorText("✘", ConsoleColor.Red);
Console.Out.WriteLine(message[1..]);
break;
case '⏭':
WriteColorText("⏭", ConsoleColor.Blue);
Console.Out.WriteLine(message[1..]);
break;
default:
Console.Out.WriteLine(message);
break;
}
}

internal static void WriteColorizedHeader(string header, string? levelShort)
{
if (string.IsNullOrWhiteSpace(header))
{
Console.Out.Write(header);
return;
}

if (header.Length >= TreeConsoleWriter.ExpectedMinHeaderLength && header[TreeConsoleWriter.BracketStartIndex] == '[')
{
WriteColorText("[", ConsoleColor.DarkGray);
WriteColorText(header.Substring(TreeConsoleWriter.TimestampStartIndex, TreeConsoleWriter.ExpectedTimestampLength), ConsoleColor.Gray);
Console.Out.Write(" ");
WriteColorText(header.Substring(TreeConsoleWriter.LevelStartIndex, TreeConsoleWriter.ExpectedLevelLength), GetLevelColor(levelShort ?? "UNK"));
WriteColorText(header.Substring(TreeConsoleWriter.BracketEndIndex), ConsoleColor.DarkGray);
}
else
{
Console.Out.Write(header);
}
}

internal static void WriteColorText(string text, ConsoleColor color)
{
Console.ForegroundColor = color;
Console.Out.Write(text);
Console.ResetColor();
}

internal static ConsoleColor GetTreeColor(int index)
=> _treeColors[Math.Max(0, index) % _treeColors.Length];

private static ConsoleColor GetLevelColor(string levelShort) => levelShort switch
{
"FTL" => ConsoleColor.Magenta,
"ERR" => ConsoleColor.Red,
"WRN" => ConsoleColor.Yellow,
"INF" => ConsoleColor.Green,
"DBG" => ConsoleColor.Cyan,
"VRB" => ConsoleColor.DarkGray,
_ => ConsoleColor.White
};

internal static string LevelToShort(LogEventLevel level) => level switch
{
LogEventLevel.Verbose => "VRB",
LogEventLevel.Debug => "DBG",
LogEventLevel.Information => "INF",
LogEventLevel.Warning => "WRN",
LogEventLevel.Error => "ERR",
LogEventLevel.Fatal => "FTL",
_ => "UNK",
};
}
Loading
Loading