Skip to content

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

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft

Add logfile actuator #1499

wants to merge 25 commits into from

Conversation

tscrypter
Copy link
Contributor

@tscrypter tscrypter commented Apr 3, 2025

Description

This PR adds a Logfile actuator. This endpoint will expose the contents of an logfile when it's configured. To log to a file, the application will need to make use of Third Party Logging providers.

The Third-party logging provider is responsible for log rotation.

This actuator can be configured with settings prefixed with Management:Endpoints:Logfile:

Key Description Default
Enabled Whether the endpoint is enabled true
ID The unique ID of the endpoint logfile
Path The relative path at which the endpoint is exposed logfile
RequiredPermissions Permissions required to access the endpoint, when running on Cloud Foundry Restricted
AllowedVerbs An array of HTTP verbs the endpoint is exposed at. GET
FilePath The path to the log file that will be exposed. If relative, will be relative to the applications working directory.

Fixes #1496

Quality checklist

  • Your code complies with our Coding Style.
  • You've updated unit and/or integration tests for your change, where applicable.
  • You've updated documentation for your change, where applicable.
    If your change affects other repositories, such as Documentation, Samples and/or MainSite, add linked PRs here.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.
  • You've added required license files and/or file headers (explaining where the code came from with proper attribution), where code is copied from StackOverflow, a blog, or OSS.

* 🧪 Add tests for endpoint options

Add tests ensuring defaults are sell and can be overridden.

* ✅ Implement endpoint options

* ♻️ Refactor endpoint options and tests

Fix warnings in tests as well, add reference in property docs, and update public APIs

* 🧪 Add failing test for relative path

Add a test that ensures config with relative path is relative to entry assembly

* ✅ Add GetLogFilePath method

Implement the GetLogFilePath to support relative paths

* ♻️ Cleanup GetFilePath

removed the hardcoding of the path to make use of the configuration options

* 🧪 Test Options

✅ Implement test for Options

* ♻️ Add tests to fix GetLogFilePath

* 🧪 Add test for missing filepath config

* ✅ Handle missing filepath config

* ♻️ Make getlogfile more readable

* 🧪 Add InvokeAsync test

* ✅ Implement InvokeAsync

InvokeAsync will return the contents of the log file

* 🧪 Add test for InvokeAsync with no filepath

When no file path is configured, InvokeAsync should return an empty string

* ✅ Handle no file path in InvokeAsync

* register with All actuators

* Add to ActuatorHostBuilderTest

* 🧪 Add middleware test
@tscrypter
Copy link
Contributor Author

I'll update this PR with a link to a documentation update PR. I also wanted to check if the Management samples would be the correct one to update.

@TimHess
Copy link
Member

TimHess commented Apr 3, 2025

Hi @tscrypter,
Thanks for getting this going, its looking like a good start. Some notes:

  • Continue to ignore any failure with the Steeltoe Sonar Analysis check (if it happens again), we're working on that separately
  • Can you change it to "Log File" and LogFile instead of "Logfile" or Logfile in code and comments (the path /actuator/logfile is fine though)
  • Do you have any concerns with using File.ReadAllTextAsync on larger text files? I don't know what the best option will be, but this also doesn't seem particularly compatible with reading a range of the file

@tscrypter
Copy link
Contributor Author

Hi @tscrypter, Thanks for getting this going, its looking like a good start. Some notes:

  • Continue to ignore any failure with the Steeltoe Sonar Analysis check (if it happens again), we're working on that separately
  • Can you change it to "Log File" and LogFile instead of "Logfile" or Logfile in code and comments (the path /actuator/logfile is fine though)
  • Do you have any concerns with using File.ReadAllTextAsync on larger text files? I don't know what the best option will be, but this also doesn't seem particularly compatible with reading a range of the file

Hi @TimHess ,

Of course, I can make those updates tonight.

Good catch on the File.ReadAllTextAsync. I can update this to read bytes while I implement the range header support.

Regarding handling large log file sizes, maybe introducing a MaxBytes config setting. If not set, the endpoint can retrieve the entire file; else, return no more than MaxBytes.

Assuming 2048 byte log file:

MaxBytes Range Header Data returned
2048 bytes
1024 first 1024 bytes
bytes=0-1024 first 1024 bytes
1024 bytes=1000-2048 bytes 1000-2024

Copy link
Member

@bart-vmware bart-vmware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Sonar issue has been fixed, please merge/rebase this PR on main. TestContext was renamed to SteeltoeTestContext, please adapt tests accordingly to fix build errors. In tests, async methods that take a CancellationToken (such as app.StartAsync and httpClient.GetAsync) should be passed TestContext.Current.CancellationToken, this is to prepare for xUnit3.

To fix PublicAPI warnings, it's easiest to clear PublicAPI.Unshipped.txt, close the file, and then use the roslyn fixer (project-wide) to recreate the file.

tscrypter and others added 9 commits April 7, 2025 08:56
Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
* Replace delimited with separated

* Review feedback
…ions for WindowsNetworkFileShare (SteeltoeOSS#1503)

* Support network shares with disk health contributor
* Embed Win32Exceptions for WindowsNetworkFileShare connection errors rather than translating

---------

Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
Copy link
Member

@bart-vmware bart-vmware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the feedback. I wrongly commented about clearing PublicAPI.Unshipped.txt. The first line (#nullable enable) needs to stay, and then the analyzer can refill it.

Do you need some tips regarding ranges and file I/O, or did you just not get to it yet?

using Microsoft.Extensions.Configuration;
using Steeltoe.Management.Endpoint.Configuration;

namespace Steeltoe.Management.Endpoint.Actuators.Logfile;
Copy link
Member

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).

tscrypter and others added 3 commits April 9, 2025 09:13
…andler.cs

Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
…ddlewareTest.cs

Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
…ddlewareTest.cs

Co-authored-by: Bart Koelman <104792814+bart-vmware@users.noreply.github.com>
@tscrypter
Copy link
Contributor Author

Thanks for addressing the feedback. I wrongly commented about clearing PublicAPI.Unshipped.txt. The first line (#nullable enable) needs to stay, and then the analyzer can refill it.

Do you need some tips regarding ranges and file I/O, or did you just not get to it yet?

Thanks @bart-vmware for the guidance. I haven't gotten to it yet, I will likely have some time this evening or tomorrow. I would like your input on the potential implementation before I refactor the code to support it.

I plan on introducing LogFileEndpointRequest and LogFileEndpointResponse classes for the additional range parameters. I'll refactor the ILogFileEndpointHandler interface with these and get the tests that are currently in place back to working.

For the LogFileEndpointMiddleware, I know I'll have to override the ParseRequestAsync to read the original HttpRequest headers, but I think I'll also have to override the WriteResponseAsync (to put the correct response headers). I also don't think I can use the base EndpointMiddleware.WriteResponseAsync because the LogFileEndpointResponse shouldn't be JsonSerialized.

@bart-vmware
Copy link
Member

Thanks for addressing the feedback. I wrongly commented about clearing PublicAPI.Unshipped.txt. The first line (#nullable enable) needs to stay, and then the analyzer can refill it.
Do you need some tips regarding ranges and file I/O, or did you just not get to it yet?

Thanks @bart-vmware for the guidance. I haven't gotten to it yet, I will likely have some time this evening or tomorrow. I would like your input on the potential implementation before I refactor the code to support it.

I plan on introducing LogFileEndpointRequest and LogFileEndpointResponse classes for the additional range parameters. I'll refactor the ILogFileEndpointHandler interface with these and get the tests that are currently in place back to working.

For the LogFileEndpointMiddleware, I know I'll have to override the ParseRequestAsync to read the original HttpRequest headers, but I think I'll also have to override the WriteResponseAsync (to put the correct response headers). I also don't think I can use the base EndpointMiddleware.WriteResponseAsync because the LogFileEndpointResponse shouldn't be JsonSerialized.

Yes, that sounds about right. Some additional thoughts:

  • Perhaps make the endpoint return IAsyncEnumerable<byte[]> (4-16k buffer) to stream large files without fully loading them into memory first? Good for scalability, but kinda conflicts with the next bullet.
  • What's a bit odd is that Spring returns Content-Type: text/plain;charset=UTF-8, but with a range it may not be proper UTF-8 at all (thinking about BOM, multi-byte chars and surrogate pairs).
  • Should return HTTP 416 on multi-range requests, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Range
  • We don't want to lock while reading a file, potentially preventing the logger from writing to it. So should probably open with FileShare.ReadWrite, but I haven't tested any of that.
  • May want to use HTTP compression if the client supports it, don't know if this is feasible

Copy link
Member

@bart-vmware bart-vmware left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice progress, I've added a few remarks.

FileInfo logFile = new FileInfo(logFilePath);
var logFileResult = new LogFileEndpointResponse(await File.ReadAllTextAsync(logFilePath, cancellationToken),
logFile.Length,
Encoding.Default, // StreamReader.CurrentEncoding is not reliable based on unit tests, so just use default for this .NET implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't make assumptions about the file encoding. It may vary per logger provider, over which Steeltoe has no control. For example, in Serilog, it can be overridden.

At the HTTP level, we're using bytes for ranges. The easiest would be to leave out charset in the response Content-Type header. If we want to include it, it should originate from the endpoint configuration that Steeltoe users must set explicitly, depending on their logger framework.

Comment on lines +17 to +18
options.AllowedVerbs.Add("Get");
options.AllowedVerbs.Add("Head");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EndpointOptions provides a virtual method GetDefaultAllowedVerbs to set these. For example usage, see LoggersEndpointOptions.

Comment on lines +9 to +11
public long? RangeStartByte { get; } = rangeStartByte;
public long? RangeLastByte { get; } = rangeLastByte;
public bool ReadContent { get; } = readContent;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As described here, both the start and end positions are optional, so using nullable is good here. But let's make LogFileEndpointRequest in the pipeline signature non-nullable. This is a public API. Otherwise, if we need to pass extra required information in the future, we need to take a breaking change. Adding an internal static get-only None property is fine if you're concerned with reducing allocations.

{
public string? Content { get; } = content;
public Encoding? LogFileEncoding { get; } = logFileEncoding;
public long? ContentLength { get; } = contentLength;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on this, we may want to add the total file size, so it can be returned in the Content-Range header.

HttpResponseMessage response = await client.SendAsync(headRequest, TestContext.Current.CancellationToken);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/plain", response.Content.Headers.ContentType?.MediaType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid using the ?. operator in assertions, because no assertion is performed if the value is null at runtime. To ensure it is non-null, assert on that first.

@bart-vmware
Copy link
Member

/azp run Steeltoe.All

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

@bart-vmware
Copy link
Member

@tscrypter Are you still working on this?

@tscrypter
Copy link
Contributor Author

@tscrypter Are you still working on this?

Hi @bart-vmware, yes I am. Sorry for the delay, some critical issues came up at work. I'm hoping to have this wrapped up this weekend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Log File actuator
3 participants