Skip to content

Commit 1a6fa44

Browse files
authored
Replace Console.WriteLine with proper logging (#195)
This closes #54 by replacing the calls to Console.WriteLine with calls to calls into Serilog. In cases where Yafc currently produces console output, it should not change that output. I was briefly tempted to write the logs to a SQLite database, but decided not to do that so the logs would be somewhat user-readable. The log files will look something like this, and will be deleted is they're over a week old: [yafc20240717.zip](https://github.com/user-attachments/files/16262567/yafc20240717.zip) This was prompted by #178 (comment), which made me think that it might be useful to log the transitions to and from the UI thread, along with their call stacks. @Dorus, I used Serilog instead of Microsoft.Extensions.Logging because there does not appear to be a Microsoft.Extensions.Logging.File nuget package. If I'm wrong there, I'm happy to use a Microsoft.Extensions.Logging-based package instead, as long as it provides structured logging. That is, something where I can easily distinguish between ["Disk quota {} set for user {}" and "Disk quota {} exceed by user {}"](https://softwareengineering.stackexchange.com/a/312586).
2 parents b2a0f46 + 16735be commit 1a6fa44

22 files changed

+194
-49
lines changed

Yafc.Model/Analysis/AutomationAnalysis.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Diagnostics;
3+
using Serilog;
4+
using Yafc.UI;
45

56
namespace Yafc.Model {
67
public enum AutomationStatus : sbyte {
78
NotAutomatable = -1, AutomatableLater = 2, AutomatableNow = 3,
89
}
910

1011
public class AutomationAnalysis : Analysis {
12+
private static readonly ILogger logger = Logging.GetLogger<AutomationAnalysis>();
1113
public static readonly AutomationAnalysis Instance = new AutomationAnalysis();
1214
public Mapping<FactorioObject, AutomationStatus> automatable;
1315

@@ -107,7 +109,7 @@ public override void Compute(Project project, ErrorCollector warnings) {
107109
}
108110
state[Database.voidEnergy] = AutomationStatus.NotAutomatable;
109111

110-
Console.WriteLine("Automation analysis (first pass) finished in " + time.ElapsedMilliseconds + " ms. Unknowns left: " + unknowns);
112+
logger.Information("Automation analysis (first pass) finished in {ElapsedTime}ms. Unknowns left: {unknownsRemaining}", time.ElapsedMilliseconds, unknowns);
111113
if (unknowns > 0) {
112114
// TODO run graph analysis if there are any unknowns left... Right now assume they are not automatable
113115
foreach (var (k, v) in state) {

Yafc.Model/Analysis/CostAnalysis.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
using System.Linq;
55
using System.Text;
66
using Google.OrTools.LinearSolver;
7+
using Serilog;
8+
using Yafc.UI;
79

810
namespace Yafc.Model {
911
public class CostAnalysis(bool onlyCurrentMilestones) : Analysis {
12+
private readonly ILogger logger = Logging.GetLogger<CostAnalysis>();
13+
1014
public static readonly CostAnalysis Instance = new CostAnalysis(false);
1115
public static readonly CostAnalysis InstanceAtMilestones = new CostAnalysis(true);
1216
public static CostAnalysis Get(bool atCurrentMilestones) {
@@ -254,12 +258,12 @@ public override void Compute(Project project, ErrorCollector warnings) {
254258
}
255259

256260
var result = workspaceSolver.TrySolveWithDifferentSeeds();
257-
Console.WriteLine("Cost analysis completed in " + time.ElapsedMilliseconds + " ms. with result " + result);
261+
logger.Information("Cost analysis completed in {ElapsedTime}ms with result {result}", time.ElapsedMilliseconds, result);
258262
float sumImportance = 1f;
259263
int totalRecipes = 0;
260264
if (result is Solver.ResultStatus.OPTIMAL or Solver.ResultStatus.FEASIBLE) {
261265
float objectiveValue = (float)objective.Value();
262-
Console.WriteLine("Estimated modpack cost: " + DataUtils.FormatAmount(objectiveValue * 1000f, UnitOfMeasure.None));
266+
logger.Information("Estimated modpack cost: {EstimatedCost}", DataUtils.FormatAmount(objectiveValue * 1000f, UnitOfMeasure.None));
263267
foreach (Goods g in Database.goods.all.ExceptExcluded(this)) {
264268
if (variables[g] == null) {
265269
continue;

Yafc.Model/Analysis/Milestones.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Linq;
5+
using Serilog;
6+
using Yafc.UI;
57

68
namespace Yafc.Model {
79
public class Milestones : Analysis {
810
public static readonly Milestones Instance = new Milestones();
911

12+
private static readonly ILogger logger = Logging.GetLogger<Milestones>();
13+
1014
public FactorioObject[] currentMilestones = [];
1115
private Mapping<FactorioObject, Bits> milestoneResult;
1216
public Bits lockedMask { get; private set; } = new();
@@ -170,7 +174,7 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
170174
Array.Resize(ref currentMilestones, nextMilestoneIndex);
171175
break;
172176
}
173-
Console.WriteLine("Processing milestone " + milestone.locName);
177+
logger.Information("Processing milestone {Milestone}", milestone.locName);
174178
processingQueue.Enqueue(milestone.id);
175179
processing[milestone] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
176180
}
@@ -228,7 +232,7 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
228232

229233
accessibleObjects++;
230234
//var obj = Database.objects[elem];
231-
//Console.WriteLine("Added object " + obj.locName + " [" + obj.GetType().Name + "] with mask " + elementFlags.ToString() + " (was " + cur.ToString() + ")");
235+
//logger.Information("Added object {LocalizedName} [{Type}] with mask {MilestoneMask} (was {PreviousMask})", obj.locName, obj.GetType().Name, elementFlags, cur);
232236
if (processing[elem] == ProcessingFlags.MilestoneNeedOrdering) {
233237
processing[elem] = 0;
234238
elementFlags |= nextMilestoneMask;
@@ -270,7 +274,7 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
270274
warnings.Error("There are some milestones that are not accessible: " + string.Join(", ", milestonesNotReachable.Select(x => x.locName)) + ". You may remove these from milestone list," +
271275
MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
272276
}
273-
Console.WriteLine("Milestones calculation finished in " + time.ElapsedMilliseconds + " ms.");
277+
logger.Information("Milestones calculation finished in {ElapsedTime}ms.", time.ElapsedMilliseconds);
274278
milestoneResult = result;
275279
}
276280

Yafc.Model/Analysis/TechnologyLoopsFinder.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
using System;
2-
using System.Linq;
1+
using System.Linq;
2+
using Serilog;
3+
using Yafc.UI;
34

45
namespace Yafc.Model {
56
public static class TechnologyLoopsFinder {
7+
private static readonly ILogger logger = Logging.GetLogger(typeof(TechnologyLoopsFinder));
8+
69
public static void FindTechnologyLoops() {
710
Graph<Technology> graph = new Graph<Technology>();
811
foreach (var technology in Database.technologies.all) {
@@ -15,12 +18,12 @@ public static void FindTechnologyLoops() {
1518
bool loops = false;
1619
foreach (var m in merged) {
1720
if (m.userData.list != null) {
18-
Console.WriteLine("Technology loop: " + string.Join(", ", m.userData.list.Select(x => x.locName)));
21+
logger.Error("Technology loop: {LoopMembers}", string.Join(", ", m.userData.list.Select(x => x.locName)));
1922
loops = true;
2023
}
2124
}
2225
if (!loops) {
23-
Console.WriteLine("No technology loops found");
26+
logger.Information("No technology loops found.");
2427
}
2528
}
2629
}

Yafc.Model/Data/DataUtils.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
using System.Text;
99
using System.Text.RegularExpressions;
1010
using Google.OrTools.LinearSolver;
11+
using Serilog;
1112
using Yafc.UI;
1213

1314
namespace Yafc.Model {
1415
public static partial class DataUtils {
16+
private static readonly ILogger logger = Logging.GetLogger(typeof(DataUtils));
17+
1518
public static readonly FactorioObjectComparer<FactorioObject> DefaultOrdering = new FactorioObjectComparer<FactorioObject>((x, y) => {
1619
float yFlow = y.ApproximateFlow();
1720
float xFlow = x.ApproximateFlow();
@@ -183,7 +186,7 @@ public static Solver.ResultStatus TrySolveWithDifferentSeeds(this Solver solver)
183186
for (int i = 0; i < 3; i++) {
184187
Stopwatch time = Stopwatch.StartNew();
185188
var result = solver.Solve();
186-
Console.WriteLine("Solution completed in " + time.ElapsedMilliseconds + " ms with result " + result);
189+
logger.Information("Solution completed in {ElapsedTime}ms with result {result}", time.ElapsedMilliseconds, result);
187190
if (result == Solver.ResultStatus.ABNORMAL) {
188191
_ = solver.SetSolverSpecificParametersAsString("random_seed:" + random.Next());
189192
continue;
@@ -197,12 +200,12 @@ public static Solver.ResultStatus TrySolveWithDifferentSeeds(this Solver solver)
197200
public static void VerySlowTryFindBadObjective(Solver solver) {
198201
var vars = solver.variables();
199202
var obj = solver.Objective();
200-
Console.WriteLine(solver.ExportModelAsLpFormat(false));
203+
logger.Information(solver.ExportModelAsLpFormat(false));
201204
foreach (var v in vars) {
202205
obj.SetCoefficient(v, 0);
203206
var result = solver.Solve();
204207
if (result == Solver.ResultStatus.OPTIMAL) {
205-
Console.WriteLine("Infeasibility candidate: " + v.Name());
208+
logger.Warning("Infeasibility candidate: {candidate}", v.Name());
206209
return;
207210
}
208211
}

Yafc.Model/Model/AutoPlanner.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using Google.OrTools.LinearSolver;
6+
using Serilog;
67
using Yafc.UI;
78

89
#nullable disable warnings // Disabling nullable in legacy code.
@@ -27,6 +28,7 @@ public class AutoPlannerRecipe {
2728
}
2829

2930
public class AutoPlanner(ModelObject page) : ProjectPageContents(page) {
31+
private static readonly ILogger logger = Logging.GetLogger<AutoPlanner>();
3032
public List<AutoPlannerGoal> goals { get; } = [];
3133
public HashSet<Recipe> done { get; } = [];
3234
public HashSet<Goods> roots { get; } = [];
@@ -105,11 +107,11 @@ public override async Task<string> Solve(ProjectPage page) {
105107
}
106108

107109
var solverResult = bestFlowSolver.Solve();
108-
Console.WriteLine("Solution completed with result " + solverResult);
110+
logger.Information("Solution completed with result {result}", solverResult);
109111
if (solverResult is not Solver.ResultStatus.OPTIMAL and not Solver.ResultStatus.FEASIBLE) {
110-
Console.WriteLine(bestFlowSolver.ExportModelAsLpFormat(false));
112+
logger.Information(bestFlowSolver.ExportModelAsLpFormat(false));
111113
this.tiers = null;
112-
return "Model have no solution";
114+
return "Model has no solution";
113115
}
114116

115117
Graph<Recipe> graph = new Graph<Recipe>();
@@ -225,7 +227,7 @@ public override async Task<string> Solve(ProjectPage page) {
225227
}
226228
}
227229
remainingNodes.Clear();
228-
Console.WriteLine("Tier creation failure");
230+
logger.Information("Tier creation failure");
229231
}
230232
tiers.Add(currentTier.Select(x => new AutoPlannerRecipe {
231233
recipe = x,

Yafc.Model/Model/ProductionTable.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using System.Threading.Tasks;
88
using Google.OrTools.LinearSolver;
9+
using Serilog;
910
using Yafc.UI;
1011

1112
namespace Yafc.Model {
@@ -16,6 +17,7 @@ public struct ProductionTableFlow(Goods goods, float amount, ProductionLink? lin
1617
}
1718

1819
public class ProductionTable : ProjectPageContents, IComparer<ProductionTableFlow>, IElementGroup<RecipeRow> {
20+
private static readonly ILogger logger = Logging.GetLogger<ProductionTable>();
1921
[SkipSerialization] public Dictionary<Goods, ProductionLink> linkMap { get; } = [];
2022
List<RecipeRow> IElementGroup<RecipeRow>.elements => recipes;
2123
[NoUndo]
@@ -408,7 +410,7 @@ private static void AddLinkCoef(Constraint cst, Variable var, ProductionLink lin
408410

409411
result = productionTableSolver.Solve();
410412

411-
Console.WriteLine("Solver finished with result " + result);
413+
logger.Information("Solver finished with result {result}", result);
412414
await Ui.EnterMainThread();
413415

414416
if (result is Solver.ResultStatus.OPTIMAL or Solver.ResultStatus.FEASIBLE) {

Yafc.Model/Serialization/ErrorCollector.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Text.Json;
5+
using Serilog;
6+
using Yafc.UI;
57

68
namespace Yafc.Model {
79
public enum ErrorSeverity {
@@ -14,6 +16,7 @@ public enum ErrorSeverity {
1416
}
1517

1618
public class ErrorCollector {
19+
private static readonly ILogger logger = Logging.GetLogger<ErrorCollector>();
1720
private readonly Dictionary<(string message, ErrorSeverity severity), int> allErrors = [];
1821
public ErrorSeverity severity { get; private set; }
1922
public void Error(string message, ErrorSeverity severity) {
@@ -24,7 +27,7 @@ public void Error(string message, ErrorSeverity severity) {
2427

2528
_ = allErrors.TryGetValue(key, out int prevC);
2629
allErrors[key] = prevC + 1;
27-
Console.WriteLine(message);
30+
logger.Information(message);
2831
}
2932

3033
public (string error, ErrorSeverity severity)[] GetArrErrors() {
@@ -54,7 +57,7 @@ public void Exception(Exception exception, string message, ErrorSeverity errorSe
5457
}
5558

5659
Error(s, errorSeverity);
57-
Console.Error.WriteLine(exception.StackTrace);
60+
logger.Error(exception, "Exception encountered");
5861
}
5962
}
6063
}

Yafc.Model/Serialization/SerializationMap.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Reflection;
55
using System.Text.Json;
6+
using Serilog;
7+
using Yafc.UI;
68

79
namespace Yafc.Model {
810
[AttributeUsage(AttributeTargets.Property)]
@@ -12,6 +14,7 @@ public class SkipSerializationAttribute : Attribute { }
1214
public class NoUndoAttribute : Attribute { }
1315

1416
internal abstract class SerializationMap {
17+
private static readonly ILogger logger = Logging.GetLogger<SerializationMap>();
1518
public UndoSnapshot MakeUndoSnapshot(ModelObject target) {
1619
UndoSnapshotBuilder snapshotBuilder = new(target);
1720
BuildUndo(target, snapshotBuilder);
@@ -40,6 +43,7 @@ public static SerializationMap GetSerializationMap(Type type) {
4043
}
4144

4245
internal static class SerializationMap<T> where T : class {
46+
private static readonly ILogger logger = Logging.GetLogger(typeof(SerializationMap<T>));
4347
private static readonly Type? parentType;
4448
private static readonly ConstructorInfo constructor;
4549
private static readonly PropertySerializer<T>[] properties;
@@ -308,7 +312,7 @@ public static void PopulateFromJson(T obj, ref Utf8JsonReader reader, Deserializ
308312
var property = FindProperty(ref reader, ref lastMatch);
309313
if (property == null || lastMatch < constructorProperties) {
310314
if (property == null) {
311-
Console.Error.WriteLine("Json has extra property: " + reader.GetString());
315+
logger.Error("Json has extra property: " + reader.GetString());
312316
}
313317

314318
reader.Skip();

Yafc.Parser/Data/FactorioDataDeserializer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
using System.Runtime.CompilerServices;
66
using System.Text;
77
using System.Threading.Tasks;
8-
using System.Xml.Linq;
98
using SDL2;
9+
using Serilog;
1010
using Yafc.Model;
1111
using Yafc.UI;
1212

1313
namespace Yafc.Parser {
1414
internal partial class FactorioDataDeserializer {
15+
private static readonly ILogger logger = Logging.GetLogger<FactorioDataDeserializer>();
1516
private LuaTable raw = null!; // null-forgiving: Initialized at the beginning of LoadData.
1617
private bool GetRef<T>(LuaTable table, string key, [MaybeNullWhen(false)] out T result) where T : FactorioObject, new() {
1718
result = null;
@@ -393,7 +394,7 @@ private void DeserializeItem(LuaTable table, ErrorCollector _) {
393394
}
394395

395396
private Fluid SplitFluid(Fluid basic, int temperature) {
396-
Console.WriteLine("Splitting fluid " + basic.name + " at " + temperature);
397+
logger.Information("Splitting fluid {FluidName} at {Temperature}", basic.name, temperature);
397398
basic.variants ??= [basic];
398399
var copy = basic.Clone();
399400
copy.SetTemperature(temperature);

0 commit comments

Comments
 (0)