Skip to content

Enables extending the Text Formatters to allow custom logic by implementations #416

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

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

josephcappellino
Copy link

Description

We have recently created a helper library that provides a common implementation of the Serilog interface. Unfortunately, it required a slight modification to the output JSON to be compatible with the downstream usage (hopefully, to be fixed in the future).

The current framework allows providing a custom ITextFormatter during initialization. In my case, we want the standard logic for all fields except Properties. This would require duplicating the entire class which could go out of sync with the existing framework.

These changes update the NormalTextFormatter class to be the base class for CompactTextFormatter and NamespacedTextFormatter implementations. This provides a common workflow for all implementations, as well as virtual methods that allow overriding any differences in the flow.

Another benefit to these changes is to keep the different formatters in sync, as they all should have very similar workflows and support the same top-level fields.

Checklist

  • [ ✅ ] My code follows the style guidelines of this project
  • [ ✅ ] I have performed a self-review of my own code
  • [ ✅ ] I have commented my code, particularly in hard-to-understand areas
  • [ ✅ ] I have made corresponding changes to the documentation
  • [ ✅ ] I have added tests that prove my fix is effective or that my feature works

    I did not add any new tests, but I also did not change any logic other than providing extension methods. The existing tests have covered all my changes.

Joseph Cappellino added 14 commits February 3, 2025 12:00
…erty and rendering logic

- This allows implementations to use most of the default logic without having to rewrite the entire class
- Creates methods for each tag to be written
- Provides a helper method to write a tag/value pair
- This can be used to override the tag name
- Allows reusing the same default logic
- Overrides fields as necessary
- Was writing all log levels, now writes non-Information levels
- Updates XML documentation to use `inheritdoc`
@FantasticFiasco
Copy link
Owner

Hi @josephcappellino! The helper library you are referring to, is that open source given I would like to take a glance at it?

@FantasticFiasco FantasticFiasco self-assigned this Feb 11, 2025
@josephcappellino
Copy link
Author

@FantasticFiasco, unfortunately, it is not. I could discuss with my company, but it's mostly set up for our needs and combines multiple sinks.

That being said, this tremendously helped us, so I felt compelled to make it better (while also helping us 😎).

I have played with the separately deployed fork, and it seems to work for us and it greatly reduced the complexity of our implementation.

@FantasticFiasco
Copy link
Owner

@josephcappellino Can you show me the changes you would like to apply to the JSON payload? Give me an example payload, serialized by this package without any of your changes, and then give me an example of how you would like the payload to look like.

@josephcappellino
Copy link
Author

@josephcappellino Can you show me the changes you would like to apply to the JSON payload? Give me an example payload, serialized by this package without any of your changes, and then give me an example of how you would like the payload to look like.

@FantasticFiasco, this is more or less to overcome a shortfall in the service to which we are sending logs, and this was faster/easier to do. 😉

But, basically, it comes down to Logging Scope properties and how they are processed. Below is a truncated sample of what it had generated versus what I need it to generate. The problem was that the post-processor treated "Scope" as a key and the entire array as the value, which made it very difficult to parse in the viewer.

  • Original
{
   "Message": "This is the message",
   "Properties": [
      "Prop1": "Prop1Value",
      "Prop2": "Prop2Value",
      "Scope": [
         "ScopeProp1": "ScopeProp1Value",
         "ScopeProp2": "ScopeProp2Value",
      ]
   ]
}
  • Updated
{
   "Message": "This is the message",
   "Properties": [
      "Prop1": "Prop1Value",
      "Prop2": "Prop2Value",
      "ScopeProp1": "ScopeProp1Value",
      "ScopeProp2": "ScopeProp2Value"
   ]
}

@FantasticFiasco
Copy link
Owner

Ok, so you are flattening the structure of Properties and only allow one level? What happens if there are two differently nested properties with the same key? Perhaps off topic... I want to take a second stab at the problem.

@josephcappellino
Copy link
Author

Ok, so you are flattening the structure of Properties and only allow one level? What happens if there are two differently nested properties with the same key? Perhaps off topic... I want to take a second stab at the problem.

You can, if you'd like, but the real answer is for the service we use to handle "Scope" properly as it's built into the .NET Logging Framework.

What might work for me, now that I just went through the code to do this, is the Namespaced Rendering. So, don't go too nuts. Then again, in this case, I would hate "Scope:XXX" have to be my query. 😉 I'll have to check that out tomorrow, as I'm not at my work computer today.

@FantasticFiasco
Copy link
Owner

You can, if you'd like, but the real answer is for the service we use to handle "Scope" properly as it's built into the .NET Logging Framework.

I know, but we work with what we have right? :-)

What might work for me, now that I just went through the code to do this...

Please have a look, and if you don't like it, please let me know and we'll figure out something that works for us both.

@josephcappellino
Copy link
Author

I tried using the Namespace one, but not sure it's going to work for what we need in this case.

Joseph Cappellino added 4 commits February 21, 2025 18:09
- This can cause some loggers to separate each item in the payload to separate entries
- Adds override of `WriteProperties` to include the ','
Joseph Cappellino added 8 commits March 4, 2025 17:33
- This allows the tests to pass on non-Windows machines
- The instance field was being reset on subsequent formatting if handled too close together
- Updates many locations to use `JsonValueFormatter.WriteQuotedJsonString()`
- Updates places to use `Write()` to simplify the logic
… on the input formatter

- This was due to a change where `NormalTextWriter` no longer wrote a new line after each entry
- This is a useful feature, and is used to keep the request payloads to single lines
- The file sinks require each log entry to be added to a single line (i.e. - `\n` is the separator)
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.

2 participants