Skip to content

Commit 20ad9d6

Browse files
author
Christoph Bühler
committed
feat(logging): add structured json logs by default on production
This closes #12. The structured logs are turned on by default when compiling in release mode. To disable the structured json logs, use the `--no-structured-logs` flag when running the operator.
1 parent 34c8f17 commit 20ad9d6

File tree

6 files changed

+162
-0
lines changed

6 files changed

+162
-0
lines changed

src/KubeOps/Operator/Commands/RunOperator.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ public RunOperator(IHost host)
1919
_host = host;
2020
}
2121

22+
[Option(
23+
KubernetesOperator.NoStructuredLogs,
24+
Description = "If set, the normal .net logging output appears instead of structured json output.")]
25+
public bool NoStructuredLogs { get; set; }
26+
2227
public Task OnExecuteAsync() => _host.RunAsync();
2328
}
2429
}

src/KubeOps/Operator/KubernetesOperator.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Reflection;
45
using System.Threading.Tasks;
56
using k8s;
67
using KubeOps.Operator.Client;
78
using KubeOps.Operator.Commands;
89
using KubeOps.Operator.DependencyInjection;
10+
using KubeOps.Operator.Logging;
911
using KubeOps.Operator.Serialization;
1012
using McMaster.Extensions.CommandLineUtils;
1113
using Microsoft.Extensions.DependencyInjection;
1214
using Microsoft.Extensions.Hosting;
15+
using Microsoft.Extensions.Logging;
1316
using Newtonsoft.Json;
1417
using Newtonsoft.Json.Converters;
1518
using Newtonsoft.Json.Serialization;
@@ -19,6 +22,7 @@ namespace KubeOps.Operator
1922
{
2023
public sealed class KubernetesOperator
2124
{
25+
internal const string NoStructuredLogs = "--no-structured-logs";
2226
private const string DefaultOperatorName = "KubernetesOperator";
2327
private readonly OperatorSettings _operatorSettings;
2428

@@ -47,6 +51,28 @@ public Task<int> Run(string[] args)
4751
ConfigureRequiredServices();
4852

4953
var app = new CommandLineApplication<RunOperator>();
54+
55+
_builder.ConfigureLogging(builder =>
56+
{
57+
builder.ClearProviders();
58+
#if DEBUG
59+
builder.AddConsole(options => options.TimestampFormat = @"[hh:mm:ss] ");
60+
#else
61+
if (args.Contains(NoStructuredLogs))
62+
{
63+
builder.AddConsole(options =>
64+
{
65+
options.TimestampFormat = @"[dd.MM.yyyy - hh:mm:ss] ";
66+
options.DisableColors = true;
67+
});
68+
}
69+
else
70+
{
71+
builder.AddStructuredConsole();
72+
}
73+
#endif
74+
});
75+
5076
var host = _builder.Build();
5177

5278
app
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.DependencyInjection.Extensions;
3+
using Microsoft.Extensions.Logging;
4+
using Microsoft.Extensions.Logging.Configuration;
5+
6+
namespace KubeOps.Operator.Logging
7+
{
8+
public static class LoggingBuilderExtensions
9+
{
10+
public static ILoggingBuilder AddStructuredConsole(this ILoggingBuilder builder)
11+
{
12+
builder.AddConfiguration();
13+
14+
builder.Services.TryAddEnumerable(ServiceDescriptor
15+
.Singleton<ILoggerProvider, StructuredConsoleLoggerProvider>());
16+
17+
return builder;
18+
}
19+
}
20+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace KubeOps.Operator.Logging
4+
{
5+
internal class LoggingNullScope : IDisposable
6+
{
7+
public void Dispose()
8+
{
9+
}
10+
}
11+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Microsoft.Extensions.Logging;
5+
using Newtonsoft.Json;
6+
7+
namespace KubeOps.Operator.Logging
8+
{
9+
internal class StructuredConsoleLogger : ILogger
10+
{
11+
private const string OriginalFormat = "{OriginalFormat}";
12+
private readonly string _category;
13+
private readonly JsonSerializerSettings _jsonSettings;
14+
15+
public StructuredConsoleLogger(string category, JsonSerializerSettings jsonSettings)
16+
{
17+
_category = category;
18+
_jsonSettings = jsonSettings;
19+
}
20+
21+
public IDisposable BeginScope<TState>(TState state) => new LoggingNullScope();
22+
23+
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
24+
25+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
26+
{
27+
var message = new LogMessage
28+
{
29+
Category = _category,
30+
Message = formatter(state, exception),
31+
Timestamp = DateTime.UtcNow,
32+
};
33+
34+
if (state is IReadOnlyList<KeyValuePair<string, object>> parameters)
35+
{
36+
message.Parameters = new Dictionary<string, object>();
37+
foreach (var (key, value) in parameters.Where(pair => pair.Key != OriginalFormat))
38+
{
39+
message.Parameters.Add(key, value);
40+
}
41+
}
42+
43+
if (exception != null)
44+
{
45+
message.ExceptionMessage = exception.Message;
46+
}
47+
48+
Console.WriteLine(JsonConvert.SerializeObject(message, _jsonSettings));
49+
}
50+
51+
private struct LogMessage
52+
{
53+
public string Category { get; set; }
54+
55+
public DateTime Timestamp { get; set; }
56+
57+
public string Message { get; set; }
58+
59+
public string ExceptionMessage { get; set; }
60+
61+
public IDictionary<string, object> Parameters { get; set; }
62+
}
63+
}
64+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using Microsoft.Extensions.Logging;
5+
using Newtonsoft.Json;
6+
7+
namespace KubeOps.Operator.Logging
8+
{
9+
internal class StructuredConsoleLoggerProvider : ILoggerProvider
10+
{
11+
private readonly JsonSerializerSettings _jsonSettings;
12+
13+
private readonly IDictionary<string, StructuredConsoleLogger> _loggers =
14+
new ConcurrentDictionary<string, StructuredConsoleLogger>();
15+
16+
public StructuredConsoleLoggerProvider(JsonSerializerSettings jsonSettings)
17+
{
18+
_jsonSettings = jsonSettings;
19+
_jsonSettings.NullValueHandling = NullValueHandling.Ignore;
20+
}
21+
22+
public void Dispose()
23+
{
24+
}
25+
26+
public ILogger CreateLogger(string categoryName)
27+
{
28+
if (!_loggers.ContainsKey(categoryName))
29+
{
30+
_loggers[categoryName] = new StructuredConsoleLogger(categoryName, _jsonSettings);
31+
}
32+
33+
return _loggers[categoryName];
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)