Skip to content

Commit fe850ca

Browse files
authored
[Apple mobile] Refactor device log capturing to use log collect command (#1456)
* Refactor device log capturing to use log collect command * Fix tests * Use log show to convert the .logarchive to readable text * Improve device log capturing by adding bundle identifier support for log filtering * Refactor device log capturing to use filtering * Remove --end argument * Remove --predicate argument * Filter output using predicate * Update log collection command to use sudo * Fix escaping in log show command * Refactor log collection to only copy system logs * Revert argument changes * Add logging * Fix log filtering * Fix filtering * Remove filtering * Copy application logs to main * Test scouting queues * Improve log collection error handling * Revert to default queues * Remove process manager * Update log filtering to include system logs in CopyLogsToMainLog method * Fix comment
1 parent 4c17e23 commit fe850ca

File tree

6 files changed

+100
-67
lines changed

6 files changed

+100
-67
lines changed

src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private async Task<ProcessExecutionResult> RunDeviceApp(
206206
CancellationToken cancellationToken)
207207
{
208208
using var deviceSystemLog = _logs.Create($"device-{device.Name}-{_helpers.Timestamp}.log", LogType.SystemLog.ToString());
209-
using var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, device.Name);
209+
using var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, device.UDID);
210210
deviceLogCapturer.StartCapture();
211211

212212
await crashReporter.StartCaptureAsync();

src/Microsoft.DotNet.XHarness.Apple/AppOperations/AppTester.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ private async Task RunDeviceTests(
371371
var deviceSystemLog = _logs.Create($"device-{device.Name}-{_helpers.Timestamp}.log", LogType.SystemLog.ToString());
372372
deviceSystemLog.Timestamp = false;
373373

374-
var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, device.Name);
374+
var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, device.UDID);
375375
deviceLogCapturer.StartCapture();
376376

377377
try

src/Microsoft.DotNet.XHarness.Apple/DeviceLogCapturerFactory.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,11 @@ namespace Microsoft.DotNet.XHarness.Apple;
1111

1212
public interface IDeviceLogCapturerFactory
1313
{
14-
IDeviceLogCapturer Create(ILog mainLog, ILog deviceLog, string deviceName);
14+
IDeviceLogCapturer Create(ILog mainLog, ILog deviceLog, string deviceUdid);
1515
}
1616

1717
public class DeviceLogCapturerFactory : IDeviceLogCapturerFactory
1818
{
19-
private readonly IMlaunchProcessManager _processManager;
20-
21-
public DeviceLogCapturerFactory(IMlaunchProcessManager processManager)
22-
{
23-
_processManager = processManager ?? throw new ArgumentNullException(nameof(processManager));
24-
}
25-
26-
public IDeviceLogCapturer Create(ILog mainLog, ILog deviceLog, string deviceName) => new DeviceLogCapturer(_processManager, mainLog, deviceLog, deviceName);
19+
public IDeviceLogCapturer Create(ILog mainLog, ILog deviceLog, string deviceUdid) => new DeviceLogCapturer(mainLog, deviceLog, deviceUdid);
2720
}
2821

src/Microsoft.DotNet.XHarness.iOS.Shared/Logging/DeviceLogCapturer.cs

Lines changed: 89 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
using System;
66
using System.Diagnostics;
7+
using System.IO;
8+
using System.Text;
79
using Microsoft.DotNet.XHarness.Common.Logging;
810
using Microsoft.DotNet.XHarness.iOS.Shared.Execution;
911

@@ -17,85 +19,123 @@ public interface IDeviceLogCapturer : IDisposable
1719

1820
public class DeviceLogCapturer : IDeviceLogCapturer
1921
{
20-
private readonly IMlaunchProcessManager _processManager;
2122
private readonly ILog _mainLog;
2223
private readonly ILog _deviceLog;
23-
private readonly string _deviceName;
24+
private readonly string _deviceUdid;
25+
private readonly string _outputPath;
26+
private DateTime _startTime;
2427

25-
public DeviceLogCapturer(IMlaunchProcessManager processManager, ILog mainLog, ILog deviceLog, string deviceName)
28+
public DeviceLogCapturer(ILog mainLog, ILog deviceLog, string deviceUdid)
2629
{
27-
_processManager = processManager ?? throw new ArgumentNullException(nameof(processManager));
2830
_mainLog = mainLog ?? throw new ArgumentNullException(nameof(mainLog));
2931
_deviceLog = deviceLog ?? throw new ArgumentNullException(nameof(deviceLog));
30-
_deviceName = deviceName ?? throw new ArgumentNullException(nameof(deviceName));
31-
}
32+
_deviceUdid = deviceUdid ?? throw new ArgumentNullException(nameof(deviceUdid));
3233

33-
private Process _process;
34+
_outputPath = Path.Combine(Path.GetTempPath(), $"device_logs_{Guid.NewGuid()}.logarchive");
35+
}
3436

3537
public void StartCapture()
3638
{
37-
var args = new MlaunchArguments
38-
{
39-
new SdkRootArgument(_processManager.XcodeRoot),
40-
new LogDevArgument(),
41-
new DeviceNameArgument(_deviceName),
42-
};
43-
44-
_process = new Process();
45-
_process.StartInfo.FileName = _processManager.MlaunchPath;
46-
_process.StartInfo.Arguments = args.AsCommandLine();
47-
_process.StartInfo.UseShellExecute = false;
48-
_process.StartInfo.RedirectStandardOutput = true;
49-
_process.StartInfo.RedirectStandardError = true;
50-
_process.StartInfo.RedirectStandardInput = true;
51-
_process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
39+
_startTime = DateTime.Now;
40+
_deviceLog.WriteLine($"Device log capture started at {_startTime:yyyy-MM-dd HH:mm:ss}");
41+
}
42+
43+
public void StopCapture()
44+
{
45+
_deviceLog.WriteLine($"Device log capture stopped at {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
46+
47+
string startTimeStr = _startTime.ToString("yyyy-MM-dd HH:mm:ss");
48+
49+
// Collect logs
50+
string collectArguments = $"log collect --device-udid {_deviceUdid} --start \"{startTimeStr}\" --output \"{_outputPath}\"";
51+
_deviceLog.WriteLine($"Collecting logs: sudo {collectArguments}");
52+
53+
using Process collectProcess = new Process();
54+
collectProcess.StartInfo.FileName = "sudo";
55+
collectProcess.StartInfo.Arguments = collectArguments;
56+
collectProcess.StartInfo.UseShellExecute = false;
57+
collectProcess.StartInfo.RedirectStandardOutput = true;
58+
collectProcess.StartInfo.RedirectStandardError = true;
59+
60+
StringBuilder collectOutput = new StringBuilder();
61+
StringBuilder collectErrors = new StringBuilder();
62+
63+
collectProcess.OutputDataReceived += (sender, e) =>
5264
{
5365
if (e.Data != null)
54-
{
55-
return;
56-
}
66+
collectOutput.AppendLine(e.Data);
67+
};
5768

58-
lock (_deviceLog)
59-
{
60-
_deviceLog.WriteLine(e.Data);
61-
}
69+
collectProcess.ErrorDataReceived += (sender, e) =>
70+
{
71+
if (e.Data != null)
72+
collectErrors.AppendLine(e.Data);
6273
};
6374

64-
_process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>
75+
collectProcess.Start();
76+
collectProcess.BeginOutputReadLine();
77+
collectProcess.BeginErrorReadLine();
78+
collectProcess.WaitForExit();
79+
80+
if (collectErrors.Length > 0)
6581
{
66-
if (e.Data == null)
82+
_mainLog.WriteLine($"Errors during log collection: {collectErrors}");
83+
84+
if (collectProcess.ExitCode != 0)
6785
{
86+
_deviceLog.WriteLine($"Log collection failed with exit code {collectProcess.ExitCode}. Skipping log reading.");
6887
return;
6988
}
89+
}
7090

71-
lock (_deviceLog)
72-
{
73-
_deviceLog.WriteLine(e.Data);
74-
}
91+
// Read the collected logs
92+
string readArguments = $"show \"{_outputPath}\"";
93+
_deviceLog.WriteLine($"Reading logs: log {readArguments}");
94+
95+
using Process readProcess = new Process();
96+
readProcess.StartInfo.FileName = "log";
97+
readProcess.StartInfo.Arguments = readArguments;
98+
readProcess.StartInfo.UseShellExecute = false;
99+
readProcess.StartInfo.RedirectStandardOutput = true;
100+
readProcess.StartInfo.RedirectStandardError = true;
101+
102+
StringBuilder output = new StringBuilder();
103+
StringBuilder errors = new StringBuilder();
104+
105+
readProcess.OutputDataReceived += (sender, e) =>
106+
{
107+
if (e.Data != null)
108+
output.AppendLine(e.Data);
75109
};
76110

77-
_deviceLog.WriteLine("{0} {1}", _process.StartInfo.FileName, _process.StartInfo.Arguments);
111+
readProcess.ErrorDataReceived += (sender, e) =>
112+
{
113+
if (e.Data != null)
114+
errors.AppendLine(e.Data);
115+
};
78116

79-
_process.Start();
80-
_process.BeginOutputReadLine();
81-
_process.BeginErrorReadLine();
82-
}
117+
readProcess.Start();
118+
readProcess.BeginOutputReadLine();
119+
readProcess.BeginErrorReadLine();
120+
readProcess.WaitForExit();
83121

84-
public void StopCapture()
85-
{
86-
if (_process.HasExited)
122+
if (output.Length > 0)
87123
{
88-
return;
124+
lock (_deviceLog)
125+
{
126+
_deviceLog.WriteLine(output.ToString());
127+
}
89128
}
90129

91-
_process.StandardInput.WriteLine();
92-
if (_process.WaitForExit((int)TimeSpan.FromSeconds(5).TotalMilliseconds))
130+
if (errors.Length > 0)
93131
{
94-
return;
132+
_mainLog.WriteLine($"Errors while reading device logs: {errors}");
95133
}
96134

97-
_processManager.KillTreeAsync(_process, _mainLog, diagnostics: false).Wait();
98-
_process.Dispose();
135+
if (Directory.Exists(_outputPath))
136+
{
137+
Directory.Delete(_outputPath, true);
138+
}
99139
}
100140

101141
public void Dispose() => StopCapture();

tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppRunnerTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public async Task RunOnDeviceTest()
107107

108108
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
109109
deviceLogCapturerFactory
110-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
110+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
111111
.Returns(deviceLogCapturer.Object);
112112

113113
var x = _logs.Object.First();
@@ -173,7 +173,7 @@ public async Task RunOnDeviceWithAppEndSignalTest()
173173

174174
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
175175
deviceLogCapturerFactory
176-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
176+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
177177
.Returns(deviceLogCapturer.Object);
178178

179179
var testEndSignal = Guid.NewGuid();
@@ -354,7 +354,7 @@ public async Task RunOnDeviceNoWaitTest()
354354

355355
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
356356
deviceLogCapturerFactory
357-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
357+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
358358
.Returns(deviceLogCapturer.Object);
359359

360360
var x = _logs.Object.First();

tests/Microsoft.DotNet.XHarness.Apple.Tests/AppOperations/AppTesterTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public async Task TestOnDeviceTest(bool useTunnel)
166166

167167
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
168168
deviceLogCapturerFactory
169-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
169+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
170170
.Returns(deviceLogCapturer.Object);
171171

172172
var testResultFilePath = Path.GetTempFileName();
@@ -264,7 +264,7 @@ public async Task TestOnDeviceWithSkippedTestsTest(params string[] skippedTests)
264264

265265
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
266266
deviceLogCapturerFactory
267-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
267+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
268268
.Returns(deviceLogCapturer.Object);
269269

270270
var testResultFilePath = Path.GetTempFileName();
@@ -346,7 +346,7 @@ public async Task TestOnDeviceWithSkippedClassesTestTest(params string[] skipped
346346

347347
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
348348
deviceLogCapturerFactory
349-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
349+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
350350
.Returns(deviceLogCapturer.Object);
351351

352352
var testResultFilePath = Path.GetTempFileName();
@@ -495,7 +495,7 @@ public async Task TestOnDeviceWithAppEndSignalTest()
495495

496496
var deviceLogCapturerFactory = new Mock<IDeviceLogCapturerFactory>();
497497
deviceLogCapturerFactory
498-
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, DeviceName))
498+
.Setup(x => x.Create(_mainLog.Object, deviceSystemLog.Object, s_mockDevice.UDID))
499499
.Returns(deviceLogCapturer.Object);
500500

501501
var testResultFilePath = Path.GetTempFileName();

0 commit comments

Comments
 (0)