Backport logging updates (#755)

pull/759/head
Next Turn 2020-12-27 01:05:46 +08:00 committed by GitHub
parent 48015a5d7a
commit 8b06b6157d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 112 deletions

View File

@ -1,16 +0,0 @@
using System.Diagnostics;
namespace WinSW.Logging
{
/// <summary>
/// Indicates that the class may reference the event log
/// </summary>
public interface IServiceEventLogProvider
{
/// <summary>
/// Locates Event Log for the service.
/// </summary>
/// <returns>Event Log or null if it is not avilable</returns>
EventLog? Locate();
}
}

View File

@ -39,10 +39,10 @@ namespace WinSW
// Currently there is no opportunity to alter the executable path // Currently there is no opportunity to alter the executable path
public virtual string ExecutablePath => Defaults.ExecutablePath; public virtual string ExecutablePath => Defaults.ExecutablePath;
public ServiceDescriptor(string baseName, DirectoryInfo d) public ServiceDescriptor(string baseName, string directory)
{ {
this.BaseName = baseName; this.BaseName = baseName;
this.BasePath = Path.Combine(d.FullName, this.BaseName); this.BasePath = Path.Combine(directory, this.BaseName);
try try
{ {
@ -54,7 +54,7 @@ namespace WinSW
} }
// register the base directory as environment variable so that future expansions can refer to this. // register the base directory as environment variable so that future expansions can refer to this.
Environment.SetEnvironmentVariable("BASE", d.FullName); Environment.SetEnvironmentVariable("BASE", directory);
// ditto for ID // ditto for ID
Environment.SetEnvironmentVariable("SERVICE_ID", this.Name); Environment.SetEnvironmentVariable("SERVICE_ID", this.Name);

View File

@ -11,9 +11,9 @@ namespace WinSW
public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings(); public static DefaultWinSWSettings Defaults { get; } = new DefaultWinSWSettings();
public ServiceDescriptorYaml(string baseName, DirectoryInfo d) public ServiceDescriptorYaml(string baseName, string directory)
{ {
string basepath = Path.Combine(d.FullName, baseName); string basepath = Path.Combine(directory, baseName);
using (var reader = new StreamReader(basepath + ".yml")) using (var reader = new StreamReader(basepath + ".yml"))
{ {
@ -23,7 +23,7 @@ namespace WinSW
this.Configurations = deserializer.Deserialize<YamlConfiguration>(file); this.Configurations = deserializer.Deserialize<YamlConfiguration>(file);
} }
Environment.SetEnvironmentVariable("BASE", d.FullName); Environment.SetEnvironmentVariable("BASE", directory);
// ditto for ID // ditto for ID
Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Name); Environment.SetEnvironmentVariable("SERVICE_ID", this.Configurations.Name);

View File

@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="log4net" Version="2.0.8" /> <PackageReference Include="log4net" Version="2.0.12" />
<PackageReference Include="YamlDotNet" Version="8.1.2" /> <PackageReference Include="YamlDotNet" Version="8.1.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.*"> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -19,19 +19,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0-windows'"> <ItemGroup Condition="'$(TargetFramework)' == 'net5.0-windows'">
<PackageReference Include="System.Diagnostics.EventLog" Version="5.0.0" />
<PackageReference Include="System.Security.AccessControl" Version="5.0.0" /> <PackageReference Include="System.Security.AccessControl" Version="5.0.0" />
</ItemGroup>
<!-- error NU1605: Detected package downgrade: log4net 2.0.8 -->
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0-windows'">
<PackageReference Include="System.Diagnostics.Debug" Version="4.3.0" />
<PackageReference Include="System.IO.FileSystem" Version="4.3.0" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="System.Runtime.Extensions" Version="4.3.1" />
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" /> <PackageReference Include="System.ServiceProcess.ServiceController" Version="5.0.0" />
<PackageReference Include="System.Threading" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'"> <ItemGroup Condition="'$(TargetFramework)' != 'net5.0-windows'">

View File

@ -8,18 +8,33 @@ namespace WinSW.Logging
/// Implementes service Event log appender for log4j. /// Implementes service Event log appender for log4j.
/// The implementation presumes that service gets initialized after the logging. /// The implementation presumes that service gets initialized after the logging.
/// </summary> /// </summary>
public class ServiceEventLogAppender : AppenderSkeleton internal sealed class ServiceEventLogAppender : AppenderSkeleton
{ {
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. private readonly WrapperServiceEventLogProvider provider;
public IServiceEventLogProvider Provider { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. internal ServiceEventLogAppender(WrapperServiceEventLogProvider provider)
{
this.provider = provider;
}
protected override void Append(LoggingEvent loggingEvent) protected override void Append(LoggingEvent loggingEvent)
{ {
var eventLog = this.Provider.Locate(); var eventLog = this.provider.Locate();
// We write the event iff the provider is ready if (eventLog is not null)
eventLog?.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level)); {
eventLog.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));
return;
}
try
{
using var backupLog = new EventLog("Application", ".", "Windows Service Wrapper");
backupLog.WriteEntry(loggingEvent.RenderedMessage, ToEventLogEntryType(loggingEvent.Level));
}
catch
{
}
} }
private static EventLogEntryType ToEventLogEntryType(Level level) private static EventLogEntryType ToEventLogEntryType(Level level)

View File

@ -5,14 +5,14 @@ namespace WinSW.Logging
/// <summary> /// <summary>
/// Implements caching of the WindowsService reference in WinSW. /// Implements caching of the WindowsService reference in WinSW.
/// </summary> /// </summary>
public class WrapperServiceEventLogProvider : IServiceEventLogProvider internal sealed class WrapperServiceEventLogProvider
{ {
public WrapperService? Service { get; set; } public WrapperService? Service { get; set; }
public EventLog? Locate() public EventLog? Locate()
{ {
var service = this.Service; var service = this.Service;
if (service != null && !service.IsShuttingDown) if (service is not null && !service.IsShuttingDown)
{ {
return service.EventLog; return service.EventLog;
} }

View File

@ -6,9 +6,7 @@ using System.IO;
#if VNEXT #if VNEXT
using System.IO.Pipes; using System.IO.Pipes;
#endif #endif
#if NET
using System.Reflection; using System.Reflection;
#endif
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
using System.ServiceProcess; using System.ServiceProcess;
@ -37,6 +35,15 @@ namespace WinSW
private static readonly ILog Log = LogManager.GetLogger(typeof(Program)); private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
private static string ExecutablePath
{
get
{
using var current = Process.GetCurrentProcess();
return current.MainModule!.FileName!;
}
}
public static int Main(string[] args) public static int Main(string[] args)
{ {
try try
@ -81,11 +88,7 @@ namespace WinSW
bool inConsoleMode = argsArray.Length > 0; bool inConsoleMode = argsArray.Length > 0;
// If descriptor is not specified, initialize the new one (and load configs from there) // If descriptor is not specified, initialize the new one (and load configs from there)
descriptor ??= GetServiceDescriptor(); descriptor ??= LoadConfigAndInitLoggers(inConsoleMode);
// Configure the wrapper-internal logging.
// STDOUT and STDERR of the child process will be handled independently.
InitLoggers(descriptor, inConsoleMode);
if (!inConsoleMode) if (!inConsoleMode)
{ {
@ -598,8 +601,6 @@ namespace WinSW
// [DoesNotReturn] // [DoesNotReturn]
void Elevate() void Elevate()
{ {
using var current = Process.GetCurrentProcess();
#if VNEXT #if VNEXT
string? stdinName = Console.IsInputRedirected ? Guid.NewGuid().ToString() : null; string? stdinName = Console.IsInputRedirected ? Guid.NewGuid().ToString() : null;
string? stdoutName = Console.IsOutputRedirected ? Guid.NewGuid().ToString() : null; string? stdoutName = Console.IsOutputRedirected ? Guid.NewGuid().ToString() : null;
@ -624,7 +625,7 @@ namespace WinSW
{ {
UseShellExecute = true, UseShellExecute = true,
Verb = "runas", Verb = "runas",
FileName = current.MainModule!.FileName!, FileName = ExecutablePath,
Arguments = arguments, Arguments = arguments,
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
}; };
@ -664,7 +665,7 @@ namespace WinSW
} }
} }
private static void InitLoggers(IWinSWConfiguration descriptor, bool enableConsoleLogging) private static IWinSWConfiguration LoadConfigAndInitLoggers(bool inConsoleMode)
{ {
// TODO: Make logging levels configurable // TODO: Make logging levels configurable
var fileLogLevel = Level.Debug; var fileLogLevel = Level.Debug;
@ -674,15 +675,47 @@ namespace WinSW
var consoleLogLevel = Level.Info; var consoleLogLevel = Level.Info;
var eventLogLevel = Level.Warn; var eventLogLevel = Level.Warn;
// Legacy format from winsw-1.x: (DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
var layout = new PatternLayout { ConversionPattern = "%d %-5p - %m%n" }; var layout = new PatternLayout { ConversionPattern = "%d %-5p - %m%n" };
layout.ActivateOptions(); layout.ActivateOptions();
var appenders = new List<IAppender>(); var repository = LogManager.GetRepository(Assembly.GetExecutingAssembly());
if (inConsoleMode)
{
var consoleAppender = new ConsoleAppender
{
Name = "Wrapper console log",
Threshold = consoleLogLevel,
Layout = layout,
};
consoleAppender.ActivateOptions();
BasicConfigurator.Configure(repository, consoleAppender);
}
else
{
var eventLogAppender = new ServiceEventLogAppender(WrapperService.eventLogProvider)
{
Name = "Wrapper event log",
Threshold = eventLogLevel,
};
eventLogAppender.ActivateOptions();
BasicConfigurator.Configure(repository, eventLogAppender);
}
string executablePath = ExecutablePath;
string directory = Path.GetDirectoryName(executablePath)!;
string baseName = Path.GetFileNameWithoutExtension(executablePath);
IWinSWConfiguration config =
File.Exists(Path.Combine(directory, baseName + ".xml")) ? new ServiceDescriptor(baseName, directory) :
File.Exists(Path.Combine(directory, baseName + ".yml")) ? new ServiceDescriptorYaml(baseName, directory).Configurations :
throw new FileNotFoundException($"Unable to locate {baseName}.[xml|yml] file within executable directory");
// .wrapper.log // .wrapper.log
string wrapperLogPath = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log"); string wrapperLogPath = Path.Combine(config.LogDirectory, config.BaseName + ".wrapper.log");
var wrapperLog = new FileAppender var fileAppender = new FileAppender
{ {
AppendToFile = true, AppendToFile = true,
File = wrapperLogPath, File = wrapperLogPath,
@ -692,37 +725,11 @@ namespace WinSW
LockingModel = new FileAppender.MinimalLock(), LockingModel = new FileAppender.MinimalLock(),
Layout = layout, Layout = layout,
}; };
wrapperLog.ActivateOptions(); fileAppender.ActivateOptions();
appenders.Add(wrapperLog);
// console log BasicConfigurator.Configure(repository, fileAppender);
if (enableConsoleLogging)
{
var consoleAppender = new ConsoleAppender
{
Name = "Wrapper console log",
Threshold = consoleLogLevel,
Layout = layout,
};
consoleAppender.ActivateOptions();
appenders.Add(consoleAppender);
}
// event log return config;
var systemEventLogger = new ServiceEventLogAppender
{
Name = "Wrapper event log",
Threshold = eventLogLevel,
Provider = WrapperService.eventLogProvider,
};
systemEventLogger.ActivateOptions();
appenders.Add(systemEventLogger);
BasicConfigurator.Configure(
#if NET
LogManager.GetRepository(Assembly.GetExecutingAssembly()),
#endif
appenders.ToArray());
} }
internal static unsafe bool IsProcessElevated() internal static unsafe bool IsProcessElevated()
@ -776,26 +783,6 @@ namespace WinSW
} }
} }
private static IWinSWConfiguration GetServiceDescriptor()
{
string executablePath = new DefaultWinSWSettings().ExecutablePath;
string baseName = Path.GetFileNameWithoutExtension(executablePath);
var d = new DirectoryInfo(Path.GetDirectoryName(executablePath)!);
if (File.Exists(Path.Combine(d.FullName, baseName + ".xml")))
{
return new ServiceDescriptor(baseName, d);
}
if (File.Exists(Path.Combine(d.FullName, baseName + ".yml")))
{
return new ServiceDescriptorYaml(baseName, d).Configurations;
}
throw new FileNotFoundException($"Unable to locate { baseName }.[xml|yml] file within executable directory");
}
private static void PrintHelp() private static void PrintHelp()
{ {
Console.WriteLine("A wrapper binary that can be used to host executables as Windows services"); Console.WriteLine("A wrapper binary that can be used to host executables as Windows services");

View File

@ -28,11 +28,7 @@ namespace WinSW
internal WinSWExtensionManager ExtensionManager { get; private set; } internal WinSWExtensionManager ExtensionManager { get; private set; }
private static readonly ILog Log = LogManager.GetLogger( private static readonly ILog Log = LogManager.GetLogger(typeof(WrapperService));
#if NET
Assembly.GetExecutingAssembly(),
#endif
"WinSW");
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new(); internal static readonly WrapperServiceEventLogProvider eventLogProvider = new();