Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/simoncropp/seqproxy
Enables writing seq logs by proxying requests through an ASP.NET Controller or Middleware.
https://github.com/simoncropp/seqproxy
Last synced: 8 days ago
JSON representation
Enables writing seq logs by proxying requests through an ASP.NET Controller or Middleware.
- Host: GitHub
- URL: https://github.com/simoncropp/seqproxy
- Owner: SimonCropp
- License: mit
- Created: 2019-05-24T00:40:49.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2024-10-28T10:51:25.000Z (19 days ago)
- Last Synced: 2024-10-29T17:23:39.315Z (17 days ago)
- Language: C#
- Homepage:
- Size: 1.02 MB
- Stars: 18
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
- Funding: .github/FUNDING.yml
- License: license.txt
- Code of conduct: code_of_conduct.md
Awesome Lists containing this project
README
# SeqProxy
[![Build status](https://ci.appveyor.com/api/projects/status/7996jd4uoooy5qy2/branch/main?svg=true)](https://ci.appveyor.com/project/SimonCropp/SeqProxy)
[![NuGet Status](https://img.shields.io/nuget/v/SeqProxy.svg)](https://www.nuget.org/packages/SeqProxy/)Enables writing [Seq](https://datalust.co/seq) logs by proxying requests through an ASP.NET Controller or Middleware.
**See [Milestones](../../milestones?state=closed) for release notes.**
## Why
* Avoid exposing the Seq API to the internet.
* Leverage [Asp Authentication and Authorization](https://docs.microsoft.com/en-us/aspnet/core/security/) to verify and control incoming requests.
* Append [extra data](#extra-data) to log messages during server processing.## NuGet package
https://nuget.org/packages/SeqProxy/
## HTTP Format/Protocol
Format: [Serilog compact](https://github.com/serilog/serilog-formatting-compact).
Protocol: [Seq raw events](https://docs.datalust.co/docs/posting-raw-events).
Note that timestamp (`@t`) is optional when using this project. If it is not supplied the server timestamp will be used.
## Extra data
For every log entry written the following information is appended:
* The current application name (as `Application`) defined in code at startup.
* The current application version (as `ApplicationVersion`) defined in code at startup.
* The server name (as `Server`) using `Environment.MachineName`.
* All claims for the current User from `ControllerBase.User.Claims`.
* The [user-agent header](https://en.wikipedia.org/wiki/User_agent) as `UserAgent`.
* The [referer header](https://en.wikipedia.org/wiki/HTTP_referer) as `Referrer`.### SeqProxyId
SeqProxyId is a tick based timestamp to help correlating a front-end error with a Seq log entry.
It is appended to every Seq log entry and returned as a header to HTTP response.
The id is generated using the following:
```cs
var startOfYear = new DateTime(utcNow.Year, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var ticks = utcNow.Ticks - startOfYear.Ticks;
var id = ticks.ToString("x");
```
snippet source | anchorWhich generates a string of the form `8e434f861302`. The current year is trimmed to shorten the id and under the assumption that retention policy is not longer than 12 months. There is a small chance of collisions, but given the use-case (error correlation), this should not impact the ability to find the correct error. This string can then be given to a user as a error correlation id.
Then the log entry can be accessed using a Seq filter.
`http://seqServer/#/events?filter=SeqProxyId%3D'39f616eeb2e3'`
## Usage
### Enable in Startup
Enable in `Startup.ConfigureServices`
```cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore(option => option.EnableEndpointRouting = false);
services.AddSeqWriter(seqUrl: "http://localhost:5341");
}
```
snippet source | anchorThere are several optional parameters:
```cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore();
services.AddSeqWriter(
seqUrl: "http://localhost:5341",
apiKey: "TheApiKey",
application: "MyAppName",
appVersion: new(1, 2),
scrubClaimType: claimType =>
{
var lastIndexOf = claimType.LastIndexOf('/');
if (lastIndexOf == -1)
{
return claimType;
}return claimType[(lastIndexOf + 1)..];
});
}
```
snippet source | anchor* `application` defaults to `Assembly.GetCallingAssembly().GetName().Name`.
* `applicationVersion` defaults to `Assembly.GetCallingAssembly().GetName().Version`.
* `scrubClaimType` is used to clean up claimtype strings. For example [ClaimTypes.Email](https://docs.microsoft.com/en-us/dotnet/api/system.identitymodel.claims.claimtypes.email?System_IdentityModel_Claims_ClaimTypes_Email) is `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`, but when recording to Seq the value `emailaddress` is sufficient. Defaults to `DefaultClaimTypeScrubber.Scrub` to get the string after the last `/`.///
/// Used for scrubbing claims when no other scrubber is defined.
///
public static class DefaultClaimTypeScrubber
{
///
/// Get the string after the last /.
///
public static CharSpan Scrub(CharSpan claimType)
{
Guard.AgainstEmpty(claimType, nameof(claimType));
var lastIndexOf = claimType.LastIndexOf('/');
if (lastIndexOf == -1)
{
return claimType;
}return claimType[(lastIndexOf + 1)..];
}
}
```
snippet source | anchor### Add HTTP handling
There are two approaches to handling the HTTP containing log events. Using a Middleware and using a Controller.
#### Using a Middleware
Using a [Middleware](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/) is done by calling `SeqWriterConfig.UseSeq` in `Startup.Configure(IApplicationBuilder builder)`:
```cs
public void Configure(IApplicationBuilder builder)
{
builder.UseSeq();
```
snippet source | anchor##### Authorization
Authorization in the middleware can bu done by using `useAuthorizationService = true` in `UseSeq`.
```cs
public void Configure(IApplicationBuilder builder)
{
builder.UseSeq(useAuthorizationService: true);
```
snippet source | anchorThis then uses [IAuthorizationService](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased) to verify the request:
```cs
async Task HandleWithAuth(HttpContext context)
{
var user = context.User;
var authResult = await authService.AuthorizeAsync(user, null, "SeqLog");if (!authResult.Succeeded)
{
await context.ChallengeAsync();
return;
}await writer.Handle(
user,
context.Request,
context.Response,
context.RequestAborted);
}
```
snippet source | anchor#### Using a Controller
`BaseSeqController` is an implementation of `ControllerBase` that provides a HTTP post and some basic routing.
///
/// An implementation of that provides a http post and some basic routing.
///
[Route("/api/events/raw")]
[Route("/seq")]
[ApiController]
public abstract class BaseSeqController :
ControllerBase
{
SeqWriter writer;///
/// Initializes a new instance of
///
protected BaseSeqController(SeqWriter writer) =>
this.writer = writer;///
/// Handles log events via a HTTP post.
///
[HttpPost]
public virtual Task Post() =>
writer.Handle(User, Request, Response, HttpContext.RequestAborted);
}
```
snippet source | anchorAdd a new [controller](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/actions) that overrides `BaseSeqController`.
```cs
public class SeqController(SeqWriter writer) :
BaseSeqController(writer);
```
snippet source | anchor##### Authorization/Authentication
Adding authorization and authentication can be done with an [AuthorizeAttribute](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple).
```cs
[Authorize]
public class SeqController(SeqWriter writer) :
BaseSeqController(writer)
```
snippet source | anchor##### Method level attributes
Method level Asp attributes can by applied by overriding `BaseSeqController.Post`.
For example adding an [exception filter ](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#exception-filters).
```cs
public class SeqController(SeqWriter writer) :
BaseSeqController(writer)
{
[CustomExceptionFilter]
public override Task Post() =>
base.Post();
```
snippet source | anchor## Client Side Usage
### Using raw JavaScript
Writing to Seq can be done using a HTTP post:
```js
function LogRawJs(text) {
const postSettings = {
method: 'POST',
credentials: 'include',
body: `{'@mt':'RawJs input: {Text}','Text':'${text}'}`
};return fetch('/api/events/raw', postSettings);
}
```
snippet source | anchor### Using Structured-Log
[structured-log](https://github.com/structured-log/structured-log/) is a structured logging framework for JavaScript, inspired by Serilog.
In combination with [structured-log-seq-sink](https://github.com/Wedvich/structured-log-seq-sink) it can be used to write to Seq
To use this approach:
#### Include the libraries
Install both [structured-log npm](https://www.npmjs.com/package/structured-log) and [structured-log-seq-sink npm](https://www.npmjs.com/package/structured-log-seq-sink). Or include them from [jsDelivr](https://www.jsdelivr.com/):
```
snippet source | anchor#### Configure the log
```js
var levelSwitch = new structuredLog.DynamicLevelSwitch('info');
const log = structuredLog.configure()
.writeTo(new structuredLog.ConsoleSink())
.minLevel(levelSwitch)
.writeTo(SeqSink({
url: `${location.protocol}//${location.host}`,
compact: true,
levelSwitch: levelSwitch
}))
.create();
```
snippet source | anchor#### Write a log message
```js
function LogStructured(text) {
log.info('StructuredLog input: {Text}', text);
}
```
snippet source | anchor#### Including data but omitting from the message template
When using structured-log, data not included in the message template will be named with a convention of `a+counter`. So for example if the following is logged:
```
log.info('The text: {Text}', text, "OtherData");
```Then `OtherData` would be written to Seq with the property name `a1`.
To work around this:
Include a filter that replaces a known token name (in this case `{@Properties}`):
```js
const logWithExtraProps = structuredLog.configure()
.filter(logEvent => {
const template = logEvent.messageTemplate;
template.raw = template.raw.replace('{@Properties}','');
return true;
})
.writeTo(SeqSink({
url: `${location.protocol}//${location.host}`,
compact: true,
levelSwitch: levelSwitch
}))
.create();
```
snippet source | anchorInclude that token name in the message template, and then include an object at the same position in the log parameters:
```js
function LogStructuredWithExtraProps(text) {
logWithExtraProps.info(
'StructuredLog input: {Text} {@Properties}',
text,
{
Timezone: new Date().getTimezoneOffset(),
Language: navigator.language
});
}
```
snippet source | anchorThen a destructured property will be written to Seq.
## Icon
[Robot](https://thenounproject.com/term/robot/883226/) designed by [Maxim Kulikov](https://thenounproject.com/maxim221) from [The Noun Project](https://thenounproject.com).