Add stdout/stderr path settings

pull/635/head
NextTurn 2020-08-05 00:00:00 +08:00 committed by Next Turn
parent 2a576e102e
commit a16c93e558
6 changed files with 129 additions and 56 deletions

View File

@ -0,0 +1,12 @@
namespace WinSW
{
public struct ProcessCommand
{
public string? Executable;
public string? Arguments;
public string? StdoutPath;
public string? StderrPath;
public LogHandler CreateLogHandler() => new TempLogHandler(this.StdoutPath, this.StderrPath);
}
}

View File

@ -9,5 +9,7 @@
internal const string Prestart = "prestart"; internal const string Prestart = "prestart";
internal const string Prestop = "prestop"; internal const string Prestop = "prestop";
internal const string Service = "service"; internal const string Service = "service";
internal const string StdoutPath = "stdoutPath";
internal const string StderrPath = "stderrPath";
} }
} }

View File

@ -253,21 +253,13 @@ namespace WinSW
} }
} }
public string? PrestartExecutable => this.GetExecutable(Names.Prestart); public ProcessCommand Prestart => this.GetProcessCommand(Names.Prestart);
public string? PrestartArguments => this.GetArguments(Names.Prestart); public ProcessCommand Poststart => this.GetProcessCommand(Names.Poststart);
public string? PoststartExecutable => this.GetExecutable(Names.Poststart); public ProcessCommand Prestop => this.GetProcessCommand(Names.Prestop);
public string? PoststartArguments => this.GetArguments(Names.Poststart); public ProcessCommand Poststop => this.GetProcessCommand(Names.Poststop);
public string? PrestopExecutable => this.GetExecutable(Names.Prestop);
public string? PrestopArguments => this.GetArguments(Names.Prestop);
public string? PoststopExecutable => this.GetExecutable(Names.Poststop);
public string? PoststopArguments => this.GetArguments(Names.Poststop);
public override string WorkingDirectory public override string WorkingDirectory
{ {
@ -746,16 +738,22 @@ namespace WinSW
return environment; return environment;
} }
private string? GetExecutable(string name) private ProcessCommand GetProcessCommand(string name)
{ {
string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Executable)?.InnerText; XmlNode? node = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name);
return text is null ? null : Environment.ExpandEnvironmentVariables(text); return node is null ? default : new ProcessCommand
} {
Executable = GetInnerText(Names.Executable),
Arguments = GetInnerText(Names.Arguments),
StdoutPath = GetInnerText(Names.StdoutPath),
StderrPath = GetInnerText(Names.StderrPath),
};
private string? GetArguments(string name) string? GetInnerText(string name)
{ {
string? text = this.dom.SelectSingleNode(Names.Service)?.SelectSingleNode(name)?.SelectSingleNode(Names.Arguments)?.InnerText; string? text = node.SelectSingleNode(name)?.InnerText;
return text is null ? null : Environment.ExpandEnvironmentVariables(text); return text is null ? null : Environment.ExpandEnvironmentVariables(text);
}
} }
} }
} }

View File

@ -14,6 +14,31 @@ namespace WinSW
void WriteEntry(string message, EventLogEntryType type); void WriteEntry(string message, EventLogEntryType type);
} }
internal sealed class TempLogHandler : AbstractFileLogAppender
{
private readonly string? outputPath;
private readonly string? errorPath;
public TempLogHandler(string? outputPath, string? errorPath)
: base(string.Empty, string.Empty, IsDisabled(outputPath), IsDisabled(errorPath), string.Empty, string.Empty)
{
this.outputPath = outputPath;
this.errorPath = errorPath;
}
private static bool IsDisabled(string? path) => string.IsNullOrEmpty(path) || path!.Equals("NUL", StringComparison.OrdinalIgnoreCase);
protected override Task LogOutput(StreamReader outputReader)
{
return this.CopyStreamAsync(outputReader, this.CreateWriter(new FileStream(this.outputPath!, FileMode.OpenOrCreate)));
}
protected override Task LogError(StreamReader errorReader)
{
return this.CopyStreamAsync(errorReader, this.CreateWriter(new FileStream(this.errorPath!, FileMode.OpenOrCreate)));
}
}
/// <summary> /// <summary>
/// Abstraction for handling log. /// Abstraction for handling log.
/// </summary> /// </summary>

View File

@ -346,45 +346,77 @@ $@"<service>
[Fact] [Fact]
public void Additional_Executable_And_Arguments() public void Additional_Executable_And_Arguments()
{ {
const string prestartExecutable = "1"; var prestart = new ProcessCommand
const string prestartArguments = "2"; {
const string poststartExecutable = "3"; Executable = "a1",
const string poststartArguments = "4"; Arguments = "a2",
const string prestopExecutable = "5"; StdoutPath = "a3",
const string prestopArguments = "6"; StderrPath = "a4",
const string poststopExecutable = "7"; };
const string poststopArguments = "8"; var poststart = new ProcessCommand
{
Executable = "a1",
Arguments = "a2",
StdoutPath = "a3",
StderrPath = "a4",
};
var prestop = new ProcessCommand
{
Executable = "a1",
Arguments = "a2",
StdoutPath = "a3",
StderrPath = "a4",
};
var poststop = new ProcessCommand
{
Executable = "a1",
Arguments = "a2",
StdoutPath = "a3",
StderrPath = "a4",
};
string seedXml = string seedXml =
$@"<service> $@"<service>
<prestart> <prestart>
<executable>{prestartExecutable}</executable> <executable>{prestart.Executable}</executable>
<arguments>{prestartArguments}</arguments> <arguments>{prestart.Arguments}</arguments>
<stdoutPath>{prestart.StdoutPath}</stdoutPath>
<stderrPath>{prestart.StderrPath}</stderrPath>
</prestart> </prestart>
<poststart> <poststart>
<executable>{poststartExecutable}</executable> <executable>{poststart.Executable}</executable>
<arguments>{poststartArguments}</arguments> <arguments>{poststart.Arguments}</arguments>
<stdoutPath>{poststart.StdoutPath}</stdoutPath>
<stderrPath>{poststart.StderrPath}</stderrPath>
</poststart> </poststart>
<prestop> <prestop>
<executable>{prestopExecutable}</executable> <executable>{prestop.Executable}</executable>
<arguments>{prestopArguments}</arguments> <arguments>{prestop.Arguments}</arguments>
<stdoutPath>{prestop.StdoutPath}</stdoutPath>
<stderrPath>{prestop.StderrPath}</stderrPath>
</prestop> </prestop>
<poststop> <poststop>
<executable>{poststopExecutable}</executable> <executable>{poststop.Executable}</executable>
<arguments>{poststopArguments}</arguments> <arguments>{poststop.Arguments}</arguments>
<stdoutPath>{poststop.StdoutPath}</stdoutPath>
<stderrPath>{poststop.StderrPath}</stderrPath>
</poststop> </poststop>
</service>"; </service>";
XmlServiceConfig config = XmlServiceConfig.FromXml(seedXml); XmlServiceConfig config = XmlServiceConfig.FromXml(seedXml);
Assert.Equal(prestartExecutable, config.PrestartExecutable); VerifyEqual(prestart, config.Prestart);
Assert.Equal(prestartArguments, config.PrestartArguments); VerifyEqual(poststart, config.Poststart);
Assert.Equal(poststartExecutable, config.PoststartExecutable); VerifyEqual(prestop, config.Prestop);
Assert.Equal(poststartArguments, config.PoststartArguments); VerifyEqual(poststop, config.Poststop);
Assert.Equal(prestopExecutable, config.PrestopExecutable);
Assert.Equal(prestopArguments, config.PrestopArguments); static void VerifyEqual(ProcessCommand expected, ProcessCommand actual)
Assert.Equal(poststopExecutable, config.PoststopExecutable); {
Assert.Equal(poststopArguments, config.PoststopArguments); Assert.Equal(expected.Executable, actual.Executable);
Assert.Equal(expected.Arguments, actual.Arguments);
Assert.Equal(expected.StdoutPath, actual.StdoutPath);
Assert.Equal(expected.StderrPath, actual.StderrPath);
}
} }
} }
} }

View File

@ -298,12 +298,13 @@ namespace WinSW
throw new AggregateException(exceptions); throw new AggregateException(exceptions);
} }
string? prestartExecutable = this.config.PrestartExecutable; ProcessCommand prestart = this.config.Prestart;
string? prestartExecutable = prestart.Executable;
if (prestartExecutable != null) if (prestartExecutable != null)
{ {
try try
{ {
using Process process = this.StartProcess(prestartExecutable, this.config.PrestartArguments); using Process process = this.StartProcess(prestartExecutable, prestart.Arguments, prestart.CreateLogHandler());
this.WaitForProcessToExit(process); this.WaitForProcessToExit(process);
this.LogExited($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); this.LogExited($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode);
process.StopDescendants(additionalStopTimeout); process.StopDescendants(additionalStopTimeout);
@ -323,10 +324,11 @@ namespace WinSW
this.ExtensionManager.FireOnWrapperStarted(); this.ExtensionManager.FireOnWrapperStarted();
LogHandler executableLogHandler = this.CreateExecutableLogHandler(); LogHandler executableLogHandler = this.CreateExecutableLogHandler();
this.process = this.StartProcess(this.config.Executable, startArguments, this.OnMainProcessExited, executableLogHandler); this.process = this.StartProcess(this.config.Executable, startArguments, executableLogHandler, this.OnMainProcessExited);
this.ExtensionManager.FireOnProcessStarted(this.process); this.ExtensionManager.FireOnProcessStarted(this.process);
string? poststartExecutable = this.config.PoststartExecutable; ProcessCommand poststart = this.config.Poststart;
string? poststartExecutable = poststart.Executable;
if (poststartExecutable != null) if (poststartExecutable != null)
{ {
try try
@ -341,7 +343,7 @@ namespace WinSW
{ {
lock (this) lock (this)
{ {
return this.startingProcess = this.StartProcess(poststartExecutable, this.config.PoststartArguments); return this.startingProcess = this.StartProcess(poststartExecutable, poststart.Arguments, poststart.CreateLogHandler());
} }
} }
} }
@ -357,12 +359,13 @@ namespace WinSW
/// </summary> /// </summary>
private void DoStop() private void DoStop()
{ {
string? prestopExecutable = this.config.PrestopExecutable; ProcessCommand prestop = this.config.Prestop;
string? prestopExecutable = prestop.Executable;
if (prestopExecutable != null) if (prestopExecutable != null)
{ {
try try
{ {
using Process process = StartProcessLocked(prestopExecutable, this.config.PrestopArguments); using Process process = StartProcessLocked(prestopExecutable, prestop.Arguments, prestop.CreateLogHandler());
this.WaitForProcessToExit(process); this.WaitForProcessToExit(process);
this.LogExited($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); this.LogExited($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode);
process.StopDescendants(additionalStopTimeout); process.StopDescendants(additionalStopTimeout);
@ -419,12 +422,13 @@ namespace WinSW
} }
} }
string? poststopExecutable = this.config.PoststopExecutable; ProcessCommand poststop = this.config.Poststop;
string? poststopExecutable = poststop.Executable;
if (poststopExecutable != null) if (poststopExecutable != null)
{ {
try try
{ {
using Process process = StartProcessLocked(poststopExecutable, this.config.PoststopArguments); using Process process = StartProcessLocked(poststopExecutable, poststop.Arguments, poststop.CreateLogHandler());
this.WaitForProcessToExit(process); this.WaitForProcessToExit(process);
this.LogExited($"Post-Stop process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode); this.LogExited($"Post-Stop process '{process.Format()}' exited with code {process.ExitCode}.", process.ExitCode);
process.StopDescendants(additionalStopTimeout); process.StopDescendants(additionalStopTimeout);
@ -446,11 +450,11 @@ namespace WinSW
Log.Info("Finished " + this.config.Name); Log.Info("Finished " + this.config.Name);
Process StartProcessLocked(string executable, string? arguments) Process StartProcessLocked(string executable, string? arguments, LogHandler? logHandler = null)
{ {
lock (this) lock (this)
{ {
return this.stoppingProcess = this.StartProcess(executable, arguments); return this.stoppingProcess = this.StartProcess(executable, arguments, logHandler);
} }
} }
} }
@ -528,7 +532,7 @@ namespace WinSW
} }
} }
private Process StartProcess(string executable, string? arguments, Action<Process>? onExited = null, LogHandler? logHandler = null) private Process StartProcess(string executable, string? arguments, LogHandler? logHandler = null, Action<Process>? onExited = null)
{ {
var startInfo = new ProcessStartInfo(executable, arguments) var startInfo = new ProcessStartInfo(executable, arguments)
{ {