Skip to content

Commit 657162f

Browse files
adding elastic search provider
1 parent 22ff130 commit 657162f

10 files changed

+353
-2
lines changed

Serilog.Ui.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{157C
1919
EndProject
2020
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleWebApp", "samples\SampleWebApp\SampleWebApp.csproj", "{30C8AE36-8117-4E52-8140-8440D3C9AD39}"
2121
EndProject
22+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Ui.ElasticSearchProvider", "src\Serilog.Ui.ElasticSearchProvider\Serilog.Ui.ElasticSearchProvider.csproj", "{4CC537BE-8695-4976-BA43-B8484664C169}"
23+
EndProject
2224
Global
2325
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2426
Debug|Any CPU = Debug|Any CPU
@@ -49,6 +51,10 @@ Global
4951
{30C8AE36-8117-4E52-8140-8440D3C9AD39}.Debug|Any CPU.Build.0 = Debug|Any CPU
5052
{30C8AE36-8117-4E52-8140-8440D3C9AD39}.Release|Any CPU.ActiveCfg = Release|Any CPU
5153
{30C8AE36-8117-4E52-8140-8440D3C9AD39}.Release|Any CPU.Build.0 = Release|Any CPU
54+
{4CC537BE-8695-4976-BA43-B8484664C169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55+
{4CC537BE-8695-4976-BA43-B8484664C169}.Debug|Any CPU.Build.0 = Debug|Any CPU
56+
{4CC537BE-8695-4976-BA43-B8484664C169}.Release|Any CPU.ActiveCfg = Release|Any CPU
57+
{4CC537BE-8695-4976-BA43-B8484664C169}.Release|Any CPU.Build.0 = Release|Any CPU
5258
EndGlobalSection
5359
GlobalSection(SolutionProperties) = preSolution
5460
HideSolutionNode = FALSE
@@ -60,6 +66,7 @@ Global
6066
{0004A882-82C9-40C3-9D6C-CD297D1A50E7} = {ACA69857-2E3E-468C-B0B0-A86852E3492D}
6167
{A6195106-0818-4CE1-A889-BB734960DD8D} = {ACA69857-2E3E-468C-B0B0-A86852E3492D}
6268
{30C8AE36-8117-4E52-8140-8440D3C9AD39} = {157CA77C-513A-409F-8045-E68739AAC8C8}
69+
{4CC537BE-8695-4976-BA43-B8484664C169} = {ACA69857-2E3E-468C-B0B0-A86852E3492D}
6370
EndGlobalSection
6471
GlobalSection(ExtensibilityGlobals) = postSolution
6572
SolutionGuid = {88374732-FEAD-4375-9CF1-75331A37CF07}

samples/SampleWebApp/SampleWebApp.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
44
<TargetFramework>net5.0</TargetFramework>
@@ -16,12 +16,16 @@
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
</PackageReference>
1818
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
19+
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="8.4.1" />
1920
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
21+
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
22+
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="8.4.1" />
2023
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="5.6.0" />
2124
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
2225
</ItemGroup>
2326

2427
<ItemGroup>
28+
<ProjectReference Include="..\..\src\Serilog.Ui.ElasticSearchProvider\Serilog.Ui.ElasticSearchProvider.csproj" />
2529
<ProjectReference Include="..\..\src\Serilog.Ui.MongoDbProvider\Serilog.Ui.MongoDbProvider.csproj" />
2630
<ProjectReference Include="..\..\src\Serilog.Ui.MsSqlServerProvider\Serilog.Ui.MsSqlServerProvider.csproj" />
2731
<ProjectReference Include="..\..\src\Serilog.Ui.PostgreSqlProvider\Serilog.Ui.PostgreSqlProvider.csproj" />

samples/SampleWebApp/Startup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.IdentityModel.Tokens;
1010
using SampleWebApp.Authentication.Jwt;
1111
using SampleWebApp.Data;
12+
using Serilog.Ui.ElasticSearchProvider.Extensions;
1213
using Serilog.Ui.MsSqlServerProvider;
1314
using Serilog.Ui.Web;
1415

@@ -44,6 +45,7 @@ public void ConfigureServices(IServiceCollection services)
4445
authOption.AuthenticationType = AuthenticationType.Jwt;
4546
authOption.Usernames = new[] { "[email protected]" };
4647
})
48+
//.UseElasticSearchDb(new System.Uri("http://localhost:9200"), "logging-index")
4749
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), "Logs"));
4850

4951
services.AddSwaggerGen();

samples/SampleWebApp/appsettings.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
"Serilog": {
1616
"Using": [ "Serilog.Sinks.MSSqlServer" ],
17-
"MinimumLevel": "Warning",
17+
"MinimumLevel": "Debug",
1818
"WriteTo": [
1919
{
2020
"Name": "MSSqlServer",
@@ -25,6 +25,31 @@
2525
"autoCreateSqlTable": true
2626
}
2727
}
28+
},
29+
{
30+
"Name": "Console",
31+
"Args": {
32+
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
33+
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
34+
}
35+
},
36+
{
37+
"Name": "Elasticsearch",
38+
"Args": {
39+
"nodeUris": "http://localhost:9200",
40+
"indexFormat": "logging-index",
41+
"batchPostingLimit": 50,
42+
"batchAction": "Create",
43+
"period": 2,
44+
"inlineFields": false,
45+
"restrictedToMinimumLevel": "Debug",
46+
"bufferBaseFilename": "C:/Temp/docker-elk-serilog-web-buffer",
47+
"connectionTimeout": 5,
48+
"emitEventFailure": "WriteToSelfLog",
49+
"queueSizeLimit": "100000",
50+
"registerTemplateFailure": "IndexAnyway",
51+
"deadLetterIndexName": "deadletter-{0:yyyy.MM}"
52+
}
2853
}
2954
]
3055
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Nest;
2+
using Serilog.Ui.Core;
3+
using Serilog.Ui.MongoDbProvider;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Serilog.Ui.ElasticSearchProvider
11+
{
12+
public class ElasticSearchDbDataProvider : IDataProvider
13+
{
14+
private readonly IElasticClient _client;
15+
16+
private readonly ElasticSearchDbOptions _options;
17+
18+
public ElasticSearchDbDataProvider(IElasticClient client, ElasticSearchDbOptions options)
19+
{
20+
_client = client ?? throw new ArgumentNullException(nameof(client));
21+
_options = options ?? throw new ArgumentNullException(nameof(options));
22+
}
23+
24+
public Task<(IEnumerable<LogModel>, int)> FetchDataAsync(int page,
25+
int count,
26+
string level = null,
27+
string searchCriteria = null)
28+
{
29+
return GetLogsAsync(page - 1, count, level, searchCriteria);
30+
}
31+
32+
private async Task<(IEnumerable<LogModel>, int)> GetLogsAsync(
33+
int page,
34+
int count,
35+
string level,
36+
string searchCriteria,
37+
CancellationToken cancellationToken = default)
38+
{
39+
var descriptor = new SearchDescriptor<ElasticSearchDbLogModel>()
40+
.Index(_options.IndexName)
41+
.Size(count)
42+
.Skip(page * count);
43+
44+
if (!string.IsNullOrEmpty(level))
45+
descriptor.Query(q => q
46+
.Match(m => m
47+
.Field(f => f.Level)
48+
.Query(level))
49+
);
50+
if (!string.IsNullOrEmpty(searchCriteria))
51+
descriptor.Query(q => q
52+
.Match(m => m
53+
.Field(f => f.Message)
54+
.Query(searchCriteria)
55+
) || q
56+
.Match(m => m
57+
.Field(f => f.Exceptions)
58+
.Query(searchCriteria)
59+
)
60+
);
61+
//descriptor = descriptor.Fields(f => f.Field(z => z.Message == searchCriteria));
62+
63+
var result = await _client.SearchAsync<ElasticSearchDbLogModel>(descriptor, cancellationToken);
64+
65+
int.TryParse(result?.Total.ToString(), out int total);
66+
67+
return (result?.Documents.Select((x, index) => x.ToLogModel(index)).ToList(), total);
68+
}
69+
}
70+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Linq;
3+
using Serilog.Ui.Core;
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Serilog.Ui.MongoDbProvider
8+
{
9+
public class ElasticSearchDbLogModel
10+
{
11+
[JsonProperty("level")]
12+
public string Level { get; set; }
13+
14+
[JsonProperty("message")]
15+
public string Message { get; set; }
16+
17+
[JsonProperty("@timestamp")]
18+
public DateTime Timestamp { get; set; }
19+
20+
public JArray Exceptions { get; set; }
21+
22+
[JsonProperty("fields")]
23+
public Dictionary<string, object> Fields { get; set; }
24+
25+
internal LogModel ToLogModel(int index)
26+
{
27+
return new LogModel
28+
{
29+
RowNo = index,
30+
Level = Level,
31+
Message = Message,
32+
Timestamp = Timestamp,
33+
Exception = Exceptions?.Count > 0 ? BuildExceptionMessage(Exceptions[0]) : string.Empty,
34+
Properties = JsonConvert.SerializeObject(Fields),
35+
PropertyType = "json"
36+
};
37+
}
38+
39+
static string BuildExceptionMessage(JToken jObjet)
40+
{
41+
return $"Exception: {jObjet.Value<string>("Message")}. StackTrace: {jObjet.Value<string>("StackTraceString")}.";
42+
}
43+
}
44+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Serilog.Ui.ElasticSearchProvider
8+
{
9+
public class ElasticSearchDbOptions
10+
{
11+
public string IndexName { get; set; }
12+
}
13+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using Elasticsearch.Net;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Nest;
4+
using Nest.JsonNetSerializer;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Serialization;
7+
using Serilog.Ui.Core;
8+
using System;
9+
using System.IO;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
namespace Serilog.Ui.ElasticSearchProvider.Extensions
14+
{
15+
public static class SerilogUiOptionBuilderExtensions
16+
{
17+
public static void UseElasticSearchDb(this SerilogUiOptionsBuilder optionsBuilder,
18+
Uri endpoint,
19+
string indexName)
20+
{
21+
if (endpoint == null) throw new ArgumentNullException(nameof(endpoint));
22+
if (string.IsNullOrEmpty(indexName)) throw new ArgumentNullException(nameof(indexName));
23+
24+
var options = new ElasticSearchDbOptions
25+
{
26+
IndexName = indexName
27+
};
28+
29+
var builder = ((ISerilogUiOptionsBuilder)optionsBuilder);
30+
31+
builder.Services.AddSingleton(options);
32+
33+
var pool = new SingleNodeConnectionPool(endpoint);
34+
//var connectionSettings1 = new ConnectionSettings(pool, sourceSerializer: JsonNetSerializer.Default);
35+
//var connectionSettings = new ConnectionSettings(pool, sourceSerializer: (builtin, values) => new CamelCaseJsonNetSerializer(builtin, values));
36+
var connectionSettings = new ConnectionSettings(pool, sourceSerializer: (builtin, values) => new VanillaSerializer());
37+
38+
//var connectionSettings = new ConnectionSettings(pool, (builtin, settings) =>
39+
// new JsonNetSerializer(builtin, settings,
40+
// modifyContractResolver: c => { c.NamingStrategy = new SnakeCaseNamingStrategy(); }
41+
// )
42+
//);
43+
44+
45+
builder.Services.AddSingleton<IElasticClient>(o => new ElasticClient(connectionSettings));
46+
builder.Services.AddScoped<IDataProvider, ElasticSearchDbDataProvider>();
47+
}
48+
}
49+
50+
class CamelCaseJsonNetSerializer : ConnectionSettingsAwareSerializerBase
51+
{
52+
public CamelCaseJsonNetSerializer(IElasticsearchSerializer builtinSerializer, IConnectionSettingsValues connectionSettings)
53+
: base(builtinSerializer, connectionSettings) { }
54+
55+
protected override void ModifyContractResolver(ConnectionSettingsAwareContractResolver resolver) =>
56+
resolver.NamingStrategy = new CamelCaseNamingStrategy();
57+
58+
public override Task<T> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default)
59+
{
60+
using (StreamReader reader = new StreamReader(stream))
61+
using (JsonTextReader jsonReader = new JsonTextReader(reader))
62+
{
63+
JsonSerializer ser = new JsonSerializer();
64+
return Task.FromResult(ser.Deserialize<T>(jsonReader));
65+
}
66+
}
67+
68+
public override Task<object> DeserializeAsync(Type type, Stream stream, CancellationToken cancellationToken = default)
69+
{
70+
using (StreamReader reader = new StreamReader(stream))
71+
using (JsonTextReader jsonReader = new JsonTextReader(reader))
72+
{
73+
JsonSerializer ser = new JsonSerializer();
74+
return Task.FromResult(ser.Deserialize(jsonReader, type));
75+
}
76+
}
77+
}
78+
79+
public class VanillaSerializer : IElasticsearchSerializer
80+
{
81+
public T Deserialize<T>(Stream stream)
82+
=> (T)Deserialize(typeof(T), stream);
83+
84+
85+
public object Deserialize(Type type, Stream stream)
86+
{
87+
#if DEBUG
88+
var memStream = new MemoryStream();
89+
stream.CopyTo(memStream);
90+
memStream.Seek(0, SeekOrigin.Begin);
91+
92+
var reader = new StreamReader(memStream);
93+
var @string = reader.ReadToEnd();
94+
95+
memStream.Seek(0, SeekOrigin.Begin);
96+
97+
using (var jreader = new JsonTextReader(reader))
98+
{
99+
var serializer = new JsonSerializer();
100+
return serializer.Deserialize(jreader, type);
101+
}
102+
#else
103+
var reader = new StreamReader(stream);
104+
using (var jreader = new JsonTextReader(reader))
105+
{
106+
var serializer = new JsonSerializer();
107+
return serializer.Deserialize(jreader, type);
108+
}
109+
#endif
110+
}
111+
112+
public Task<T> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default(CancellationToken)) =>
113+
Task.FromResult(Deserialize<T>(stream));
114+
115+
public Task<object> DeserializeAsync(Type type, Stream stream, CancellationToken cancellationToken = default(CancellationToken)) =>
116+
Task.FromResult(Deserialize(type, stream));
117+
118+
public void Serialize<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.Indented)
119+
{
120+
var writer = new StreamWriter(stream);
121+
using (var jWriter = new JsonTextWriter(writer))
122+
{
123+
var serializer = new JsonSerializer
124+
{
125+
Formatting = formatting == SerializationFormatting.Indented ? Formatting.Indented : Formatting.None
126+
};
127+
serializer.Serialize(jWriter, data);
128+
}
129+
}
130+
131+
132+
public Task SerializeAsync<T>(T data, Stream stream, SerializationFormatting formatting = SerializationFormatting.Indented,
133+
CancellationToken cancellationToken = default(CancellationToken))
134+
{
135+
Serialize<T>(data, stream, formatting);
136+
return Task.CompletedTask;
137+
}
138+
}
139+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
9+
<PackageReference Include="NEST" Version="7.11.1" />
10+
<PackageReference Include="NEST.JsonNetSerializer" Version="7.11.1" />
11+
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\Serilog.Ui.Core\Serilog.Ui.Core.csproj" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)