-
Notifications
You must be signed in to change notification settings - Fork 163
Add logfile actuator #1499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add logfile actuator #1499
Changes from 5 commits
37d4b74
a0e8fe2
711c98f
ee6cf15
7f8d4f9
e48a003
d361f82
26511de
f1f808b
ee63559
88324e5
52cd3b0
5f98eca
04eb095
013e333
5367550
2b016ef
416957d
a65eb25
361636e
629afc2
5c1f8aa
5679892
fefff15
f106e48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Steeltoe.Management.Endpoint.Configuration; | ||
|
||
namespace Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
internal sealed class ConfigureLogFileEndpointOptions : ConfigureEndpointOptions<LogFileEndpointOptions> | ||
{ | ||
private const string ManagementInfoPrefix = "management:endpoints:logfile"; | ||
|
||
public ConfigureLogFileEndpointOptions(IConfiguration configuration) | ||
: base(configuration, ManagementInfoPrefix, "logfile") | ||
{ | ||
ArgumentNullException.ThrowIfNull(configuration); | ||
tscrypter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public override void Configure(LogFileEndpointOptions options) | ||
{ | ||
ArgumentNullException.ThrowIfNull(options); | ||
|
||
base.Configure(options); | ||
} | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Hosting; | ||
|
||
namespace Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
public static class EndpointServiceCollectionExtensions | ||
Check failure on line 11 in src/Management/src/Endpoint/Actuators/LogFile/EndpointServiceCollectionExtensions.cs
|
||
{ | ||
/// <summary> | ||
/// Adds the logfile actuator to the service container and configure the ASP.NET Core middleware pipeline. | ||
tscrypter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// </summary> | ||
/// <param name="services"> | ||
/// The <see cref="IServiceCollection" /> to add services to. | ||
/// </param> | ||
/// <returns> | ||
/// The incoming <paramref name="services" /> so that additional calls can be chained. | ||
/// </returns> | ||
public static IServiceCollection AddLogFileActuator(this IServiceCollection services) | ||
{ | ||
return AddLogFileActuator(services, true); | ||
} | ||
|
||
/// <summary> | ||
/// Adds the logfile actuator to the service container. | ||
/// </summary> | ||
/// <param name="services"> | ||
/// The <see cref="IServiceCollection" /> to add services to. | ||
/// </param> | ||
/// <param name="configureMiddleware"> | ||
/// When <c>false</c>, skips configuration of the ASP.NET Core middleware pipeline. While this provides full control over the pipeline order, it requires | ||
/// manual addition of the appropriate middleware for actuators to work correctly. | ||
/// </param> | ||
/// <returns> | ||
/// The incoming <paramref name="services" /> so that additional calls can be chained. | ||
/// </returns> | ||
public static IServiceCollection AddLogFileActuator(this IServiceCollection services, bool configureMiddleware) | ||
{ | ||
ArgumentNullException.ThrowIfNull(services); | ||
|
||
services.TryAddSingleton<IHostEnvironment>(serviceProvider => serviceProvider.GetService<IHostEnvironment>()!); | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
services.AddCoreActuatorServices<LogFileEndpointOptions, ConfigureLogFileEndpointOptions, LogFileEndpointMiddleware, | ||
ILogFileEndpointHandler, LogFileEndpointHandler, object?, string>(configureMiddleware); | ||
|
||
return services; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
namespace Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
public interface ILogFileEndpointHandler : IEndpointHandler<object?, string>; | ||
Check failure on line 7 in src/Management/src/Endpoint/Actuators/LogFile/ILogFileEndpointHandler.cs
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Reflection; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using Steeltoe.Management.Configuration; | ||
|
||
namespace Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
public sealed class LogFileEndpointHandler : ILogFileEndpointHandler | ||
Check failure on line 12 in src/Management/src/Endpoint/Actuators/LogFile/LogFileEndpointHandler.cs
|
||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly IOptionsMonitor<LogFileEndpointOptions> _optionsMonitor; | ||
private readonly ILogger<LogFileEndpointHandler> _logger; | ||
|
||
public EndpointOptions Options => _optionsMonitor.CurrentValue; | ||
|
||
public LogFileEndpointHandler(IOptionsMonitor<LogFileEndpointOptions> optionsMonitorMonitor, ILoggerFactory loggerFactory) | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
ArgumentNullException.ThrowIfNull(optionsMonitorMonitor); | ||
ArgumentNullException.ThrowIfNull(loggerFactory); | ||
|
||
_optionsMonitor = optionsMonitorMonitor; | ||
_logger = loggerFactory.CreateLogger<LogFileEndpointHandler>(); | ||
} | ||
|
||
public async Task<string> InvokeAsync(object? argument, CancellationToken cancellationToken) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this returned There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To support byte ranges, |
||
{ | ||
_logger.LogTrace("Invoking LogfileEndpointHandler with argument: {Argument}", argument); | ||
tscrypter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
string logFilePath = GetLogFilePath(); | ||
|
||
if (!string.IsNullOrEmpty(logFilePath)) | ||
{ | ||
return await File.ReadAllTextAsync(logFilePath, cancellationToken); | ||
} | ||
|
||
_logger.LogWarning("Log file path is not set"); | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return string.Empty; | ||
} | ||
|
||
internal string GetLogFilePath() | ||
{ | ||
_logger.LogTrace("Getting log file path"); | ||
|
||
if (!string.IsNullOrEmpty(_optionsMonitor.CurrentValue.FilePath)) | ||
{ | ||
string entryAssemblyDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!; | ||
return Path.Combine(entryAssemblyDirectory, _optionsMonitor.CurrentValue.FilePath ?? string.Empty); | ||
} | ||
|
||
_logger.LogWarning("File path is not set"); | ||
return string.Empty; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Options; | ||
using Steeltoe.Management.Endpoint.Configuration; | ||
using Steeltoe.Management.Endpoint.Middleware; | ||
|
||
namespace Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
internal sealed class LogFileEndpointMiddleware( | ||
ILogFileEndpointHandler endpointHandler, IOptionsMonitor<ManagementOptions> managementOptionsMonitor, ILoggerFactory loggerFactory) | ||
: EndpointMiddleware<object?, string>(endpointHandler, managementOptionsMonitor, loggerFactory) | ||
{ | ||
protected override async Task<string> InvokeEndpointHandlerAsync(object? request, CancellationToken cancellationToken) | ||
{ | ||
return await EndpointHandler.InvokeAsync(request, cancellationToken); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Steeltoe.Management.Configuration; | ||
|
||
namespace Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
public sealed class LogFileEndpointOptions : EndpointOptions | ||
Check failure on line 9 in src/Management/src/Endpoint/Actuators/LogFile/LogFileEndpointOptions.cs
|
||
{ | ||
/// <summary> | ||
/// Gets or sets the file path to the log file on disk. The path can be absolute or relative to | ||
tscrypter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// <see cref="System.Reflection.Assembly.GetEntryAssembly()" />. | ||
/// </summary> | ||
public string? FilePath { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Net; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.Extensions.Configuration; | ||
using Steeltoe.Common.TestResources; | ||
using Steeltoe.Common.TestResources.IO; | ||
|
||
namespace Steeltoe.Management.Endpoint.Test.Actuators.Logfile; | ||
|
||
public sealed class EndpointMiddlewareTest : BaseTest | ||
{ | ||
private static readonly Dictionary<string, string?> AppSettings = new() | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
["Logging:Console:IncludeScopes"] = "false", | ||
["Logging:LogLevel:Default"] = "Warning", | ||
["Logging:LogLevel:TestApp"] = "Information", | ||
["Logging:LogLevel:Steeltoe"] = "Information", | ||
["management:endpoints:enabled"] = "true", | ||
["management:endpoints:actuator:exposure:include:0"] = "*" | ||
}; | ||
|
||
private readonly TempFile _tempLogFile = new(); | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[Fact] | ||
public async Task LogFileActuator_ReturnsExpectedData() | ||
{ | ||
AppSettings["management:endpoints:logfile:filePath"] = _tempLogFile.FullPath; | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await File.WriteAllTextAsync(_tempLogFile.FullPath, "This is a test log."); | ||
|
||
WebHostBuilder builder = TestWebHostBuilderFactory.Create(); | ||
builder.UseStartup<Startup>(); | ||
builder.ConfigureAppConfiguration((_, configuration) => configuration.AddInMemoryCollection(AppSettings)); | ||
|
||
using IWebHost app = builder.Build(); | ||
await app.StartAsync(); | ||
|
||
using HttpClient client = app.GetTestClient(); | ||
HttpResponseMessage response = await client.GetAsync(new Uri("http://localhost/actuator/logfile")); | ||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); | ||
|
||
string actualLogContent = await response.Content.ReadAsStringAsync(); | ||
|
||
// assert | ||
actualLogContent.Trim().Should().BeEquivalentTo("\"This is a test log.\""); | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
using Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
namespace Steeltoe.Management.Endpoint.Test.Actuators.Logfile; | ||
|
||
public sealed class EndpointServiceCollectionTest : BaseTest | ||
{ | ||
[Fact] | ||
public async Task AddLogFileActuator_AddsCorrectServices() | ||
{ | ||
var services = new ServiceCollection(); | ||
services.AddSingleton(TestHostEnvironmentFactory.Create()); | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
var appSettings = new Dictionary<string, string?> | ||
{ | ||
["management:endpoints:logfile:path"] = "/some", | ||
["management:endpoints:logfile:filePath"] = "/var/logs/app.log" | ||
bart-vmware marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
var configurationBuilder = new ConfigurationBuilder(); | ||
configurationBuilder.AddInMemoryCollection(appSettings); | ||
IConfiguration configuration = configurationBuilder.Build(); | ||
|
||
services.AddSingleton(configuration); | ||
services.AddLogging(); | ||
services.AddLogFileActuator(); | ||
await using ServiceProvider serviceProvider = services.BuildServiceProvider(true); | ||
|
||
var options = serviceProvider.GetRequiredService<IOptionsMonitor<LogFileEndpointOptions>>(); | ||
Assert.Equal("/some", options.CurrentValue.Path); | ||
|
||
var handler = serviceProvider.GetService<ILogFileEndpointHandler>(); | ||
Assert.NotNull(handler); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the Apache 2.0 License. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Steeltoe.Management.Configuration; | ||
using Steeltoe.Management.Endpoint.Actuators.Logfile; | ||
|
||
namespace Steeltoe.Management.Endpoint.Test.Actuators.Logfile; | ||
|
||
public sealed class LogFileEndpointOptionsTest : BaseTest | ||
{ | ||
[Fact] | ||
public void AppliesDefaults() | ||
{ | ||
LogFileEndpointOptions options = GetOptionsFromSettings<LogFileEndpointOptions, ConfigureLogFileEndpointOptions>(); | ||
|
||
options.Id.Should().Be("logfile"); | ||
options.RequiredPermissions.Should().Be(EndpointPermissions.Restricted); | ||
options.Path.Should().Be("logfile"); | ||
options.FilePath.Should().BeNull(); | ||
options.AllowedVerbs.Should().Contain("Get"); | ||
options.AllowedVerbs.Should().HaveCount(1); | ||
} | ||
|
||
[Fact] | ||
public void CanOverrideDefaults() | ||
{ | ||
var appSettings = new Dictionary<string, string?> | ||
{ | ||
["management:endpoints:logfile:path"] = "testPath", | ||
["management:endpoints:logfile:filePath"] = "logs/application.log" | ||
}; | ||
|
||
LogFileEndpointOptions options = GetOptionsFromSettings<LogFileEndpointOptions, ConfigureLogFileEndpointOptions>(appSettings); | ||
|
||
options.Id.Should().Be("logfile"); | ||
options.RequiredPermissions.Should().Be(EndpointPermissions.Restricted); | ||
options.Path.Should().Be("testPath"); | ||
options.AllowedVerbs.Should().Contain("Get"); | ||
options.FilePath.Should().Be("logs/application.log"); | ||
options.AllowedVerbs.Should().Contain("Get"); | ||
options.AllowedVerbs.Should().HaveCount(1); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The namespaces still have the old casing (also in tests).