-
Notifications
You must be signed in to change notification settings - Fork 2
/
ClientCountryEnricher.cs
146 lines (130 loc) · 5.47 KB
/
ClientCountryEnricher.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using MaxMind.GeoIP2;
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
namespace UpdaterMirror;
/// <summary>
/// Extensions for enriching log events with client country information
/// </summary>
public static class ClientInfoLoggerConfigurationExtensions
{
/// <summary>
/// Enrich log events with the client country information
/// </summary>
/// <param name="enrichmentConfiguration">The logger enrichment configuration</param>
/// <param name="accountId">The MaxMind account id</param>
/// <param name="licenseKey">The MaxMind license key</param>
/// <param name="headerName">The header name to use for the client IP address</param>
/// <returns>The logger configuration enriched with the client country information</returns>
public static LoggerConfiguration WithClientCountry(
this LoggerEnrichmentConfiguration enrichmentConfiguration,
int accountId,
string licenseKey,
string headerName
)
{
if (enrichmentConfiguration == null)
throw new ArgumentNullException(nameof(enrichmentConfiguration));
return enrichmentConfiguration.With(new ClientCountryEnricher(headerName, accountId, licenseKey));
}
}
/// <summary>
/// Enriches log events with the client country information.
/// This is mostly based on Serilog's built-in `ClientIpEnricher`
/// </summary>
public class ClientCountryEnricher : ILogEventEnricher
{
/// <summary>
/// The property name for the client country
/// </summary>
private const string CountryPropertyName = "ClientCountry";
/// <summary>
/// The key for the country item in the HTTP context
/// </summary>
private const string CountryItemKey = "Serilog_ClientCountry";
/// <summary>
/// The header key to use for the client IP address
/// </summary>
private readonly string _forwardHeaderKey;
/// <summary>
/// The HTTP context accessor
/// </summary>
private readonly IHttpContextAccessor _contextAccessor;
/// <summary>
/// The MaxMind web service client
/// </summary>
private readonly WebServiceClient _webserviceClient;
/// <summary>
/// Initializes a new instance of the <see cref="ClientCountryEnricher"/> class
/// </summary>
/// <param name="forwardHeaderKey">The header key to use for the client IP address</param>
/// <param name="accountId">The MaxMind account id</param>
/// <param name="licenseKey">The MaxMind license key</param>
public ClientCountryEnricher(string forwardHeaderKey, int accountId, string licenseKey)
: this(forwardHeaderKey, new WebServiceClient(accountId, licenseKey), new HttpContextAccessor())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientCountryEnricher"/> class
/// </summary>
/// <param name="forwardHeaderKey">The header key to use for the client IP address</param>
/// <param name="webServiceClient">The MaxMind web service client</param>
/// <param name="contextAccessor">The HTTP context accessor</param>
internal ClientCountryEnricher(string forwardHeaderKey, WebServiceClient webServiceClient, IHttpContextAccessor contextAccessor)
{
_forwardHeaderKey = string.IsNullOrWhiteSpace(forwardHeaderKey)
? "x-forwarded-for"
: forwardHeaderKey;
_contextAccessor = contextAccessor;
_webserviceClient = webServiceClient;
}
/// <summary>
/// Enriches the log event with the client country information
/// </summary>
/// <param name="logEvent">The log event to enrich</param>
/// <param name="propertyFactory">The log event property factory</param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var httpContext = _contextAccessor.HttpContext;
if (httpContext == null)
return;
if (httpContext.Items[CountryItemKey] is LogEventProperty logEventProperty)
{
logEvent.AddPropertyIfAbsent(logEventProperty);
return;
}
var ipAddress = GetIpAddress();
var country = "unknown";
if (!string.IsNullOrWhiteSpace(ipAddress))
try { country = _webserviceClient.Country(ipAddress).Country.IsoCode; }
catch (Exception ex)
{
country = "error";
var errorProperty = new LogEventProperty(CountryPropertyName + "Error", new ScalarValue(ex.Message));
httpContext.Items.Add(CountryItemKey + "Error", errorProperty);
logEvent.AddPropertyIfAbsent(errorProperty);
}
var countryProperty = new LogEventProperty(CountryPropertyName, new ScalarValue(country));
httpContext.Items.Add(CountryItemKey, countryProperty);
logEvent.AddPropertyIfAbsent(countryProperty);
}
/// <summary>
/// Gets the client IP address
/// </summary>
private string? GetIpAddress()
{
var ipAddress = _contextAccessor.HttpContext?.Request?.Headers[_forwardHeaderKey].FirstOrDefault();
return !string.IsNullOrEmpty(ipAddress)
? GetIpAddressFromProxy(ipAddress)
: _contextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString();
}
/// <summary>
/// Gets the IP address from the proxy list
/// </summary>
private string GetIpAddressFromProxy(string proxifiedIpList)
{
var addresses = proxifiedIpList.Split(',');
return addresses.Length == 0 ? string.Empty : addresses[0].Trim();
}
}