Skip to content

Commit

Permalink
Add support for forward compatible binlog format
Browse files Browse the repository at this point in the history
  • Loading branch information
JanKrivanek committed Dec 8, 2023
1 parent dd38153 commit 9481a34
Show file tree
Hide file tree
Showing 30 changed files with 1,452 additions and 434 deletions.
30 changes: 30 additions & 0 deletions src/BinlogTool/BinlogToolCommandBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Logging.StructuredLogger;

namespace BinlogTool
{
public abstract class BinlogToolCommandBase
{
public ForwardCompatibilityReadingHandler CompatibilityHandler { protected get; init; }

protected Build ReadBuild(string binLogFilePath, bool throwOnPathNotFound = true)
{
if (string.IsNullOrEmpty(binLogFilePath) || !File.Exists(binLogFilePath))
{
if(throwOnPathNotFound)
{
throw new FileNotFoundException("Specified binlog was not found.", binLogFilePath);
}

return null;
}

return CompatibilityHandler?.ReadBuild(binLogFilePath) ?? BinaryLog.ReadBuild(binLogFilePath);
}
}
}
171 changes: 171 additions & 0 deletions src/BinlogTool/ForwardCompatibilityReadingHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Logging.StructuredLogger;

namespace BinlogTool
{
public class ForwardCompatibilityReadingHandler
{
private enum Mode
{
Disallow,
FailOnError,
LogErrorsSummary,
LogErrorsDetailed,
IgnoreErrors,
Invalid
}

private bool _isInitialized;
private Mode _mode;

private void CheckInitialized()
{
if (!_isInitialized)
{
throw new InvalidOperationException("ForwardCompatibilityReadingHandler is not initialized");
}
}

public bool ForwardCompatibilityExplicitlyConfigured { get; private set; }

public bool ProcessCommandLine(ref string[] args)
{
_isInitialized = true;

var compatArgs = args
.Where(arg =>
arg.StartsWith("--forwardCompatibility", StringComparison.CurrentCultureIgnoreCase) ||
arg.StartsWith("-fwd", StringComparison.CurrentCultureIgnoreCase))
.ToList();

if (compatArgs.Count == 0)
{
return true;
}

ForwardCompatibilityExplicitlyConfigured = true;

if (compatArgs.Count > 1)
{
Console.Error.WriteLine("Only one --forwardCompatibility/-fwd argument is allowed");
return false;
}

var compatArg = compatArgs[0];
args = args.Where(arg => arg != compatArg).ToArray();

int colonIndex = compatArg.IndexOf(':');

if (colonIndex == -1)
{
_mode = Mode.LogErrorsSummary;
return true;
}

_mode = compatArg.Substring(colonIndex + 1).ToLowerInvariant() switch
{
"d" or "disallow" => Mode.Disallow,
"f" or "failonerror" => Mode.FailOnError,
"l" or "logerrorssummary" => Mode.LogErrorsSummary,
"lv" or "logerrorsdetailed" => Mode.LogErrorsDetailed,
"i" or "ignoreerrors" => Mode.IgnoreErrors,
_ => Mode.Invalid
};

if (_mode == Mode.Invalid)
{
Console.Error.WriteLine("Invalid forward compatibility mode");
return false;
}

return true;
}

public Build ReadBuild(string binLogFilePath)
{
if (string.IsNullOrEmpty(binLogFilePath) || !File.Exists(binLogFilePath))
{
return null;
}

binLogFilePath = Path.GetFullPath(binLogFilePath);

var build = BinaryLog.ReadBuild(binLogFilePath, this.AllowForwardCompatibilityDelegate);
if (_compatibilityException != null)
{
throw _compatibilityException;
}
return build;
}

private Exception _compatibilityException = null;
public IForwardCompatibilityReadSettings AllowForwardCompatibilityDelegate
{
get
{
CheckInitialized();

IForwardCompatibilityReadSettings allowCompatSettings =
((AllowForwardCompatibilityDelegate)(_ => true)).WithDefaultHandler();

return _mode switch
{
Mode.Disallow => null,
Mode.FailOnError => allowCompatSettings.WithCustomErrorHandler(err =>
throw (_compatibilityException = new Exception(err.GetFormattedMessage()))),
_ => allowCompatSettings
};
}
}

public void HandleBuildResults(Build build)
{
CheckInitialized();

if (_mode != Mode.LogErrorsSummary && _mode != Mode.LogErrorsDetailed)
{
return;
}

var errors = build.RecoverableReadingErrors;
if (errors == null || errors.Count == 0)
{
return;
}

IEnumerable<string> summaryLines;

if (_mode == Mode.LogErrorsSummary)
{
summaryLines = errors
.GroupBy(e => e.errorType)
.Select(g =>
$"{SpacedReaderErrorType(g.Key)}: {g.Sum(e => e.count)} Total errors (in {g.GroupBy(f => f.recordKind).Count()} distinct types of records)");
}
else
{
summaryLines = errors
.Select(e => $"| {SpacedReaderErrorType(e.errorType)} | {e.recordKind} | {e.count} |")
.Prepend($"| :{new string('-', _maxErrorTypeLength-1)} | :---------- | -----------: |")
.Prepend($"| {SpacedReaderErrorType("Error Type")} | Record Kind | Errors Count |");
}

var summary = string.Join(Environment.NewLine, summaryLines);

Console.Error.WriteLine();
Console.Error.WriteLine($"Forward compatibility recoverable errors summary:");
Console.Error.WriteLine();
Console.Error.WriteLine(summary);
}

private static readonly int _maxErrorTypeLength = Enum.GetNames(typeof(ReaderErrorType)).Max(s => s.Length);
private string SpacedReaderErrorType(ReaderErrorType errorType)
=> SpacedReaderErrorType(errorType.ToString());

private string SpacedReaderErrorType(string text)
=> string.Format("{0,-" + _maxErrorTypeLength + "}", text);
}
}
8 changes: 5 additions & 3 deletions src/BinlogTool/ListTools.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Logging.StructuredLogger;

namespace BinlogTool
{
public class ListTools
public class ListTools : BinlogToolCommandBase
{
public void Run(string binLogFilePath)
{
var build = BinaryLog.ReadBuild(binLogFilePath);
var build = this.ReadBuild(binLogFilePath);
BuildAnalyzer.AnalyzeBuild(build);
var strings = build.StringTable.Instances.OrderBy(s => s).ToArray();

Expand Down Expand Up @@ -38,6 +38,8 @@ public void Run(string binLogFilePath)
{
Console.WriteLine(tool);
}

CompatibilityHandler?.HandleBuildResults(build);
}

private string GetSourceCommitId(Build build)
Expand Down
49 changes: 43 additions & 6 deletions src/BinlogTool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using Microsoft.Build.Logging.StructuredLogger;
using StructuredLogger.Utils;
using static Microsoft.Build.Logging.StructuredLogger.IForwardCompatibilityReadSettings;

namespace BinlogTool
{
Expand All @@ -19,7 +20,32 @@ binlogtool savefiles input.binlog output_path
binlogtool reconstruct input.binlog output_path
binlogtool savestrings input.binlog output.txt
binlogtool search *.binlog search string
binlogtool redact --input:path --recurse --in-place -p:list -p:of -p:secrets -p:to -p:redact");
binlogtool redact --input:path --recurse --in-place -p:list -p:of -p:secrets -p:to -p:redact
Global Options:
[--forwardCompatibility|-fwd][:specifier] - Optional. Controls forward compatibility mode. Optional mode specifier:
d|disallow - Not allowed to read logs with higher then current version.
f|failOnError - Fail if the log contains any unsupported records.
l|logErrorsSummary - Default, if mode unspecified.
Log errors summary if the log contains any unsupported records.
lv|logErrorsDetailed - Log detailed summary if the log contains any unsupported records.
i|ignoreErrors - Silently ignore any unsupported records.
Sampe usage:
// List all tools used in the build. Allow reading newer versions of the log, but fail if any unsupported records are encountered.
binlogtool --forwardCompatibility:failOnError listtools input.binlog
// List all tools used in the build. Allow reading only known versions of the log.
binlogtool listtools input.binlog
// List all tools used in the build. Allow reading newer versions of the log, log summary of errors if any unsupported records are encountered.
binlogtool listtools input.binlog -fwd
");
return;
}

ForwardCompatibilityReadingHandler forwardCompatibilityReadingHandler = new();
if (!forwardCompatibilityReadingHandler.ProcessCommandLine(ref args))
{
return;
}

Expand All @@ -30,7 +56,8 @@ binlogtool search *.binlog search string
var binlog = args[1];
var outputRoot = args[2];

new SaveFiles(args).Run(binlog, outputRoot);
new SaveFiles(args) { CompatibilityHandler = forwardCompatibilityReadingHandler }
.Run(binlog, outputRoot);
return;
}

Expand All @@ -39,7 +66,8 @@ binlogtool search *.binlog search string
var binlog = args[1];
var outputRoot = args[2];

new SaveFiles(args).Run(binlog, outputRoot, reconstruct: true);
new SaveFiles(args) { CompatibilityHandler = forwardCompatibilityReadingHandler }
.Run(binlog, outputRoot, reconstruct: true);
return;
}

Expand All @@ -48,15 +76,17 @@ binlogtool search *.binlog search string
var binlog = args[1];
var outputFile = args[2];

new SaveStrings().Run(binlog, outputFile);
new SaveStrings() { CompatibilityHandler = forwardCompatibilityReadingHandler }
.Run(binlog, outputFile);
return;
}

if (args.Length == 2 && string.Equals(firstArg, "listtools", StringComparison.OrdinalIgnoreCase))
{
var binlog = args[1];

new ListTools().Run(binlog);
new ListTools() { CompatibilityHandler = forwardCompatibilityReadingHandler }
.Run(binlog);
return;
}

Expand All @@ -70,12 +100,19 @@ binlogtool search *.binlog search string

var binlogs = args[1];
var search = string.Join(" ", args.Skip(2));
Searcher.Search(binlogs, search);
new Searcher() { CompatibilityHandler = forwardCompatibilityReadingHandler }
.Search2(binlogs, search);
return;
}

if (firstArg == "redact")
{
if (forwardCompatibilityReadingHandler.ForwardCompatibilityExplicitlyConfigured)
{
Console.Error.WriteLine(
"Forward compatibility mode will be ignored. Redact command doesn't interpret structured events - so it doesn't need to know the binlog format.");
}

List<string> redactTokens = new List<string>();
List<string> inputPaths = new List<string>();
bool recurse = false;
Expand Down
10 changes: 5 additions & 5 deletions src/BinlogTool/SaveFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace BinlogTool
{
public class SaveFiles
public class SaveFiles : BinlogToolCommandBase
{
private string[] args;

Expand All @@ -18,7 +18,8 @@ public SaveFiles(string[] args)

public void Run(string binlog, string outputDirectory, bool reconstruct = false)
{
if (string.IsNullOrEmpty(binlog) || !File.Exists(binlog))
Build build = this.ReadBuild(binlog, false);
if (build == null)
{
return;
}
Expand All @@ -29,15 +30,14 @@ public void Run(string binlog, string outputDirectory, bool reconstruct = false)
Directory.CreateDirectory(outputDirectory);
}

binlog = Path.GetFullPath(binlog);

var build = BinaryLog.ReadBuild(binlog);
SaveFilesFrom(build, outputDirectory);

if (reconstruct)
{
GenerateSources(build, outputDirectory);
}

CompatibilityHandler?.HandleBuildResults(build);
}

private void GenerateSources(Build build, string outputDirectory)
Expand Down
8 changes: 5 additions & 3 deletions src/BinlogTool/SaveStrings.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
using System.Linq;
using System.Linq;
using Microsoft.Build.Logging.StructuredLogger;

namespace BinlogTool
{
public class SaveStrings
public class SaveStrings : BinlogToolCommandBase
{
public void Run(string binLogFilePath, string outputFilePath)
{
var build = BinaryLog.ReadBuild(binLogFilePath);
var build = this.ReadBuild(binLogFilePath);
var strings = build.StringTable.Instances.OrderBy(s => s).ToArray();

Serialization.WriteStringsToFile(outputFilePath, strings);

this.CompatibilityHandler?.HandleBuildResults(build);
}
}
}
Loading

0 comments on commit 9481a34

Please sign in to comment.