Skip to content

Commit

Permalink
Merge pull request #77 from datalust/dev
Browse files Browse the repository at this point in the history
4.0.0 Release
  • Loading branch information
nblumhardt authored Apr 4, 2024
2 parents 463de94 + e53c0a9 commit 1bb0d9c
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 57 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# NLog.Targets.Seq [![NuGet Pre Release](https://img.shields.io/nuget/vpre/NLog.Targets.Seq.svg)](https://nuget.org/packages/NLog.Targets.Seq) [![Build status](https://ci.appveyor.com/api/projects/status/o22e6dq0mkftaggc?svg=true)](https://ci.appveyor.com/project/datalust/nlog-targets-seq) [![Join the chat at https://gitter.im/datalust/seq](https://img.shields.io/gitter/room/datalust/seq.svg)](https://gitter.im/datalust/seq)
# NLog.Targets.Seq [![NuGet Pre Release](https://img.shields.io/nuget/vpre/NLog.Targets.Seq.svg)](https://nuget.org/packages/NLog.Targets.Seq) [![Build status](https://ci.appveyor.com/api/projects/status/o22e6dq0mkftaggc?svg=true)](https://ci.appveyor.com/project/datalust/nlog-targets-seq)

An NLog target that writes events to [Seq](https://datalust.co/seq). The target takes full advantage of the structured logging support in **NLog 4.5** to provide hassle-free filtering, searching and analysis.

Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.100",
"version": "8.0.100",
"rollForward": "latestMinor"
}
}
4 changes: 3 additions & 1 deletion sample/Example/Example.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<TargetFrameworks>net472;net8.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AssemblyName>Example</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Example</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -18,6 +19,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.0" />
<ProjectReference Include="..\..\src\NLog.Targets.Seq\NLog.Targets.Seq.csproj" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion sample/Example/NLog.config
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<targets>
<target name="seq"
xsi:type="BufferingWrapper"
xsi:type="BufferingWrapper"
bufferSize="1000"
flushTimeout="2000"
slidingTimeout="false">
Expand Down
2 changes: 0 additions & 2 deletions sample/Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public static void Main()

// As are objects
Logger.Info(new object());

Console.ReadKey();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@
using NLog.Config;
using NLog.Layouts;

namespace NLog.Targets.Seq
namespace NLog.Targets.Seq.Layouts
{
[ThreadAgnostic]
class CompactJsonLayout : JsonLayout
{
readonly JsonAttribute
_timestampAttribute = new JsonAttribute("@t", new SimpleLayout("${date:format=o}")),
_levelAttribute = new JsonAttribute("@l", new SimpleLayout("${level}")),
_exceptionAttribute = new JsonAttribute("@x", new SimpleLayout("${exception:format=toString}")),
_messageAttribute = new JsonAttribute("@m", new FormattedMessageLayout()),
_messageTemplateAttribute = new JsonAttribute("@mt", new SimpleLayout("${onhasproperties:${message:raw=true}}"));
_timestampAttribute = new("@t", new SimpleLayout("${date:format=o}")),
_levelAttribute = new("@l", new SimpleLayout("${level}")),
_exceptionAttribute = new("@x", new SimpleLayout("${exception:format=toString}")),
_messageAttribute = new("@m", new FormattedMessageLayout()),
_messageTemplateAttribute = new("@mt", new SimpleLayout("${onhasproperties:${message:raw=true}}")),
_traceIdAttribute = new("@tr", new CurrentW3CActivityLayout(a => a.TraceId.ToHexString())),
_spanIdAttribute = new("@sp", new CurrentW3CActivityLayout(a => a.SpanId.ToHexString()));

public Layout LogLevel { get => _levelAttribute.Layout; set => _levelAttribute.Layout = value; }

public CompactJsonLayout()
{
Expand All @@ -37,7 +41,9 @@ public CompactJsonLayout()
var renderingsAttribute = new JsonAttribute("@r", new RenderingsLayout(new Lazy<IJsonConverter>(ResolveService<IJsonConverter>)), encode: false);
Attributes.Add(renderingsAttribute);
Attributes.Add(_messageAttribute);

Attributes.Add(_traceIdAttribute);
Attributes.Add(_spanIdAttribute);

IncludeEventProperties = true;
IncludeScopeProperties = true;
SuppressSpaces = true;
Expand Down
45 changes: 45 additions & 0 deletions src/NLog.Targets.Seq/Layouts/CurrentW3CActivityLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Seq Target for NLog - Copyright © Datalust and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Diagnostics;
using System.Text;
using NLog.Layouts;

namespace NLog.Targets.Seq.Layouts
{
/// <summary>
/// Formats elements of <see cref="Activity.Current"/> for inclusion in log events. Non-W3C-format activities are
/// ignored (Seq does not support the older Microsoft-proprietary hierarchical activity id format).
/// </summary>
class CurrentW3CActivityLayout: Layout
{
readonly Func<Activity, string> _format;

public CurrentW3CActivityLayout(Func<Activity, string> format)
{
_format = format;
}

protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
target.Append(GetFormattedMessage(logEvent));
}

protected override string GetFormattedMessage(LogEventInfo logEvent)
{
return Activity.Current is { IdFormat: ActivityIdFormat.W3C } activity ? _format(activity) : null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
using NLog.Config;
using NLog.Layouts;

namespace NLog.Targets.Seq
namespace NLog.Targets.Seq.Layouts
{
[ThreadAgnostic]
class FormattedMessageLayout : Layout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
using NLog.Layouts;
using NLog.MessageTemplates;

namespace NLog.Targets.Seq
namespace NLog.Targets.Seq.Layouts
{
[ThreadAgnostic]
class RenderingsLayout : Layout
Expand Down
9 changes: 5 additions & 4 deletions src/NLog.Targets.Seq/NLog.Targets.Seq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<PropertyGroup>
<Description>An NLog target that writes structured log events to Seq</Description>
<Authors>Datalust;Contributors</Authors>
<VersionPrefix>3.1.0</VersionPrefix>
<TargetFrameworks>net45;net462;netstandard2.0;net6.0</TargetFrameworks>
<VersionPrefix>4.0.0</VersionPrefix>
<TargetFrameworks>net462;netstandard2.0;net6.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyOriginatorKeyFile>../../asset/nlog-targets-seq.snk</AssemblyOriginatorKeyFile>
Expand All @@ -17,7 +17,7 @@
<RepositoryUrl>https://github.com/datalust/nlog-targets-seq.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -34,9 +34,10 @@
<PackageReference Include="NLog" Version="5.2.5" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net45' or '$(TargetFramework)' == 'net462' ">
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.0" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
25 changes: 25 additions & 0 deletions src/NLog.Targets.Seq/SeqHttpHeaderItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NLog.Config;
using NLog.Layouts;

namespace NLog.Targets.Seq
{
/// <summary>
/// Represents an additional HTTP header that wil be attached to outgoing HTTP requests made by
/// <see cref="SeqTarget"/>.
/// </summary>
[NLogConfigurationItem]
public sealed class SeqHttpHeaderItem
{
/// <summary>
/// The name of the HTTP header to add.
/// </summary>
[RequiredParameter]
public string Name { get; set; }

/// <summary>
/// The value of the HTTP header.
/// </summary>
[RequiredParameter]
public Layout Value { get; set; }
}
}
75 changes: 45 additions & 30 deletions src/NLog.Targets.Seq/SeqTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
using NLog.Common;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets.Seq.Layouts;

// ReSharper disable UnusedAutoPropertyAccessor.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global

namespace NLog.Targets.Seq
Expand All @@ -35,11 +38,10 @@ public sealed class SeqTarget : Target
Layout _serverUrl;
Layout _apiKey;
Layout _proxyAddress;
readonly JsonLayout _compactLayout = new CompactJsonLayout();
readonly CompactJsonLayout _compactLayout = new CompactJsonLayout();
readonly char[] _reusableEncodingBuffer = new char[32 * 1024]; // Avoid large-object-heap

Uri _webRequestUri;
string _headerApiKey;
HttpClient _httpClient;
LogLevel _minimumLevel = LogLevel.Trace;

Expand Down Expand Up @@ -76,6 +78,11 @@ public sealed class SeqTarget : Target
/// </summary>
public string ProxyAddress { get => (_proxyAddress as SimpleLayout)?.Text; set => _proxyAddress = value ?? string.Empty; }

/// <summary>
/// Override the mapping from NLog LogLevel to Seq LogLevel. Default output is ${level}
/// </summary>
public Layout SeqLevel { get => _compactLayout.LogLevel; set => _compactLayout.LogLevel = value; }

/// <summary>
/// Use default credentials
/// </summary>
Expand All @@ -85,7 +92,7 @@ public sealed class SeqTarget : Target
/// A list of properties that will be attached to the events.
/// </summary>
[ArrayParameter(typeof(SeqPropertyItem), "property")]
public IList<SeqPropertyItem> Properties { get; }
public IList<SeqPropertyItem> Properties { get; } = new List<SeqPropertyItem>();

/// <summary>
/// How far should the JSON serializer follow object references before backing off
Expand Down Expand Up @@ -117,12 +124,17 @@ public ISet<string> ExcludeProperties
set => _compactLayout.ExcludeProperties = value;
}

/// <summary>
/// Additional HTTP headers to attach to outgoing HTTP requests made by the sink.
/// </summary>
[ArrayParameter(typeof(SeqHttpHeaderItem), "header")]
public IList<SeqHttpHeaderItem> AdditionalHeaders { get; } = new List<SeqHttpHeaderItem>();

/// <summary>
/// Construct a <see cref="SeqTarget"/>.
/// </summary>
public SeqTarget()
{
Properties = new List<SeqPropertyItem>();
MaxRecursionLimit = 0; // Default behavior for Serilog
JsonPayloadMaxLength = 128 * 1024;
}
Expand All @@ -147,7 +159,8 @@ protected override void InitializeTarget()
uri += SeqApi.BulkUploadResource;
_webRequestUri = new Uri(uri);

_headerApiKey = _apiKey?.Render(LogEventInfo.CreateNullEvent()) ?? string.Empty;
var nullEvent = LogEventInfo.CreateNullEvent();
var headerApiKey = _apiKey?.Render(nullEvent) ?? string.Empty;

HttpClientHandler handler = null;

Expand All @@ -167,6 +180,14 @@ protected override void InitializeTarget()
}

_httpClient = handler == null ? new HttpClient() : new HttpClient(handler);

if (!string.IsNullOrWhiteSpace(headerApiKey))
_httpClient.DefaultRequestHeaders.Add(SeqApi.ApiKeyHeaderName, headerApiKey);

foreach (var additionalHeader in AdditionalHeaders)
{
_httpClient.DefaultRequestHeaders.Add(additionalHeader.Name, additionalHeader.Value.Render(nullEvent));
}
}

base.InitializeTarget();
Expand Down Expand Up @@ -265,11 +286,9 @@ async Task PostBatch(IList<AsyncLogEventInfo> logEvents)
}
}

private async Task SendPayload(StringBuilder payload)
async Task SendPayload(StringBuilder payload)
{
var request = new HttpRequestMessage(HttpMethod.Post, _webRequestUri);
if (!string.IsNullOrWhiteSpace(_headerApiKey))
request.Headers.Add(SeqApi.ApiKeyHeaderName, _headerApiKey);

var httpContent = new ByteArrayContent(EncodePayload(Utf8, payload));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(SeqApi.CompactLogEventFormatMediaType) { CharSet = Utf8.WebName };
Expand All @@ -278,41 +297,37 @@ private async Task SendPayload(StringBuilder payload)
// Even if no events are above `_minimumLevel`, we'll send a batch to make sure we observe minimum
// level changes sent by the server.

using (var response = await _httpClient.SendAsync(request).ConfigureAwait(false))
using var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
if ((int)response.StatusCode > 299)
{
if ((int)response.StatusCode > 299)
{
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new WebException($"Received failed response {response.StatusCode} from Seq server: {data}");
}
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new WebException($"Received failed response {response.StatusCode} from Seq server: {data}");
}

if ((int)response.StatusCode == (int)HttpStatusCode.Created)
if ((int)response.StatusCode == (int)HttpStatusCode.Created)
{
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var serverRequestedLevel = LevelMapping.ToNLogLevel(SeqApi.ReadMinimumAcceptedLevel(data));
if (serverRequestedLevel != _minimumLevel)
{
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var serverRequestedLevel = LevelMapping.ToNLogLevel(SeqApi.ReadMinimumAcceptedLevel(data));
if (serverRequestedLevel != _minimumLevel)
{
InternalLogger.Info("Seq(Name={0}): Setting minimum log level to {1} per server request", Name, serverRequestedLevel);
_minimumLevel = serverRequestedLevel;
}
InternalLogger.Info("Seq(Name={0}): Setting minimum log level to {1} per server request", Name, serverRequestedLevel);
_minimumLevel = serverRequestedLevel;
}
}
}

private byte[] EncodePayload(Encoding encoder, StringBuilder payload)
byte[] EncodePayload(Encoding encoder, StringBuilder payload)
{
lock (_reusableEncodingBuffer)
{
int totalLength = payload.Length;
var totalLength = payload.Length;
if (totalLength < _reusableEncodingBuffer.Length)
{
payload.CopyTo(0, _reusableEncodingBuffer, 0, payload.Length);
return encoder.GetBytes(_reusableEncodingBuffer, 0, totalLength);
}
else
{
return encoder.GetBytes(payload.ToString());
}

return encoder.GetBytes(payload.ToString());
}
}

Expand All @@ -329,9 +344,9 @@ internal void TestInitialize()
InitializeTarget();
}

private static void AddJsonAttribute(JsonLayout jsonLayout, JsonAttribute jsonAttribute)
static void AddJsonAttribute(JsonLayout jsonLayout, JsonAttribute jsonAttribute)
{
for (int i = jsonLayout.Attributes.Count - 1; i >= 0; --i)
for (var i = jsonLayout.Attributes.Count - 1; i >= 0; --i)
{
if (jsonLayout.Attributes[i].Name == jsonAttribute.Name)
{
Expand Down
Loading

0 comments on commit 1bb0d9c

Please sign in to comment.