Skip to content

Commit 1aa9a7e

Browse files
authored
Merge pull request #396 from bgrainger/logging
Add logging API. Fixes #390
2 parents c0e6ab3 + efdcf7e commit 1aa9a7e

19 files changed

+556
-67
lines changed

MySqlConnector.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio 15
3-
VisualStudioVersion = 15.0.27004.2009
3+
VisualStudioVersion = 15.0.27004.2010
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector", "src\MySqlConnector\MySqlConnector.csproj", "{F82378AF-274E-4FBA-8E45-27126D607B85}"
66
EndProject
@@ -12,6 +12,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector.Performance"
1212
EndProject
1313
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Conformance.Tests", "tests\Conformance.Tests\Conformance.Tests.csproj", "{CC0DA702-43E8-471E-9320-F36685C540A1}"
1414
EndProject
15+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector.Logging.log4net", "src\MySqlConnector.Logging.log4net\MySqlConnector.Logging.log4net.csproj", "{A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}"
16+
EndProject
1517
Global
1618
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1719
Debug|Any CPU = Debug|Any CPU
@@ -38,6 +40,10 @@ Global
3840
{CC0DA702-43E8-471E-9320-F36685C540A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
3941
{CC0DA702-43E8-471E-9320-F36685C540A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
4042
{CC0DA702-43E8-471E-9320-F36685C540A1}.Release|Any CPU.Build.0 = Release|Any CPU
43+
{A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44+
{A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Debug|Any CPU.Build.0 = Debug|Any CPU
45+
{A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Release|Any CPU.ActiveCfg = Release|Any CPU
46+
{A15647B8-FA3F-4536-BF4E-4F93F2FBFC75}.Release|Any CPU.Build.0 = Release|Any CPU
4147
EndGlobalSection
4248
GlobalSection(SolutionProperties) = preSolution
4349
HideSolutionNode = FALSE

appveyor.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ before_test:
1010
test_script:
1111
- ps: .\.ci\test.ps1
1212
after_test:
13-
- cmd: dotnet pack src\MySqlConnector\MySqlConnector.csproj -c Release -p:SourceLinkCreate=true -p:SourceLinkTest=true
13+
- cmd: |-
14+
dotnet pack src\MySqlConnector\MySqlConnector.csproj -c Release -p:SourceLinkCreate=true -p:SourceLinkTest=true
15+
dotnet pack src\MySqlConnector.Logging.log4net\MySqlConnector.Logging.log4net.csproj -c Release -p:SourceLinkCreate=true -p:SourceLinkTest=true
1416
notifications:
1517
- provider: Slack
1618
incoming_webhook:
1719
secure: SRJ5fYQE4tvelyqeX3Lkv0gXta3O2pl4/+wNaqmqmcFkmYBe+U31T5YCGhipBOVhHBIZLevihOJZ78sFVJrmAFV5bHgtX/VPIKEXN+7ytis=
1820
artifacts:
19-
- path: src\MySqlConnector\bin\Release\MySqlConnector.*.nupkg
21+
- path: src\*\bin\Release\*.nupkg
2022
deploy:
2123
- provider: NuGet
2224
api_key:

src/Directory.Build.props

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project>
2+
3+
<PropertyGroup>
4+
<VersionPrefix>0.32.0</VersionPrefix>
5+
<SignAssembly>true</SignAssembly>
6+
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
7+
<AssemblyOriginatorKeyFile>..\..\MySqlConnector.snk</AssemblyOriginatorKeyFile>
8+
<TargetFrameworks>net45;net46;netstandard1.3;netstandard2.0</TargetFrameworks>
9+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10+
<PackageReleaseNotes>https://mysql-net.github.io/MySqlConnector/overview/version-history/</PackageReleaseNotes>
11+
<PackageProjectUrl>https://mysql-net.github.io/MySqlConnector/</PackageProjectUrl>
12+
<PackageLicenseUrl>https://opensource.org/licenses/MIT</PackageLicenseUrl>
13+
<PackageIconUrl>https://mysql-net.github.io/MySqlConnector/img/logo.png</PackageIconUrl>
14+
<RepositoryType>git</RepositoryType>
15+
<RepositoryUrl>https://github.com/mysql-net/MySqlConnector.git</RepositoryUrl>
16+
<DebugType>embedded</DebugType>
17+
<LangVersion>latest</LangVersion>
18+
</PropertyGroup>
19+
20+
<ItemGroup Condition=" '$(OS)' == 'Windows_NT' ">
21+
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.5.0" PrivateAssets="All" />
22+
<DotNetCliToolReference Include="dotnet-sourcelink" Version="2.5.0" />
23+
<PackageReference Include="SourceLink.Test" Version="2.5.0" PrivateAssets="all" />
24+
</ItemGroup>
25+
26+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
27+
<DefineConstants>DEBUG</DefineConstants>
28+
</PropertyGroup>
29+
30+
</Project>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Reflection;
4+
using log4net;
5+
using log4net.Core;
6+
7+
namespace MySqlConnector.Logging
8+
{
9+
public sealed class Log4netLoggerProvider : IMySqlConnectorLoggerProvider
10+
{
11+
public IMySqlConnectorLogger CreateLogger(string name) => new Log4netLogger(LogManager.GetLogger(s_loggerAssembly, "MySqlConnector." + name));
12+
13+
static readonly Assembly s_loggerAssembly = typeof(Log4netLogger).GetTypeInfo().Assembly;
14+
static readonly Type s_loggerType = typeof(Log4netLogger);
15+
16+
private class Log4netLogger : IMySqlConnectorLogger
17+
{
18+
public Log4netLogger(ILoggerWrapper log) => m_logger = log.Logger;
19+
20+
public bool IsEnabled(MySqlConnectorLogLevel level) => m_logger.IsEnabledFor(GetLevel(level));
21+
22+
public void Log(MySqlConnectorLogLevel level, string message, object[] args = null, Exception exception = null)
23+
{
24+
if (args == null || args.Length == 0)
25+
m_logger.Log(s_loggerType, GetLevel(level), message, exception);
26+
else
27+
m_logger.Log(s_loggerType, GetLevel(level), string.Format(CultureInfo.InvariantCulture, message, args), exception);
28+
}
29+
30+
private static Level GetLevel(MySqlConnectorLogLevel level)
31+
{
32+
switch (level)
33+
{
34+
case MySqlConnectorLogLevel.Trace:
35+
return Level.Trace;
36+
case MySqlConnectorLogLevel.Debug:
37+
return Level.Debug;
38+
case MySqlConnectorLogLevel.Info:
39+
return Level.Info;
40+
case MySqlConnectorLogLevel.Warn:
41+
return Level.Warn;
42+
case MySqlConnectorLogLevel.Error:
43+
return Level.Error;
44+
case MySqlConnectorLogLevel.Fatal:
45+
return Level.Fatal;
46+
default:
47+
throw new ArgumentOutOfRangeException(nameof(level), level, "Invalid value for 'level'.");
48+
}
49+
}
50+
51+
readonly ILogger m_logger;
52+
}
53+
}
54+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net45;net46;netstandard1.3;netstandard2.0</TargetFrameworks>
5+
<Title>MySQL Connector Logging Adapter for log4net</Title>
6+
<Copyright>Copyright 2017 Bradley Grainger</Copyright>
7+
<Authors>Bradley Grainger</Authors>
8+
<PackageTags>mysql;mysqlconnector;async;ado.net;database;netcore</PackageTags>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="log4net" Version="2.0.8" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\MySqlConnector\MySqlConnector.csproj" />
17+
</ItemGroup>
18+
19+
</Project>

src/MySqlConnector/Core/ConnectionPool.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66
using MySql.Data.MySqlClient;
7+
using MySqlConnector.Logging;
78
using MySqlConnector.Protocol.Serialization;
9+
using MySqlConnector.Utilities;
810

911
namespace MySqlConnector.Core
1012
{
1113
internal sealed class ConnectionPool
1214
{
15+
public int Id { get; }
16+
1317
public async Task<ServerSession> GetSessionAsync(MySqlConnection connection, IOBehavior ioBehavior, CancellationToken cancellationToken)
1418
{
1519
cancellationToken.ThrowIfCancellationRequested();
@@ -18,12 +22,16 @@ public async Task<ServerSession> GetSessionAsync(MySqlConnection connection, IOB
1822
// check at most once per second (although this isn't enforced via a mutex so multiple threads might block
1923
// on the lock in RecoverLeakedSessions in high-concurrency situations
2024
if (m_sessionSemaphore.CurrentCount == 0 && unchecked(((uint) Environment.TickCount) - m_lastRecoveryTime) >= 1000u)
25+
{
26+
Log.Warn("{0} is empty; recovering leaked sessions", m_logArguments);
2127
RecoverLeakedSessions();
28+
}
2229

2330
if (m_connectionSettings.MinimumPoolSize > 0)
2431
await CreateMinimumPooledSessions(ioBehavior, cancellationToken).ConfigureAwait(false);
2532

2633
// wait for an open slot (until the cancellationToken is cancelled, which is typically due to timeout)
34+
Log.Debug("{0} waiting for an available session", m_logArguments);
2735
if (ioBehavior == IOBehavior.Asynchronous)
2836
await m_sessionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
2937
else
@@ -43,10 +51,12 @@ public async Task<ServerSession> GetSessionAsync(MySqlConnection connection, IOB
4351
}
4452
if (session != null)
4553
{
54+
Log.Debug("{0} found an existing session; checking it for validity", m_logArguments);
4655
bool reuseSession;
4756

4857
if (session.PoolGeneration != m_generation)
4958
{
59+
Log.Debug("{0} discarding session due to wrong generation", m_logArguments);
5060
reuseSession = false;
5161
}
5262
else
@@ -64,26 +74,41 @@ public async Task<ServerSession> GetSessionAsync(MySqlConnection connection, IOB
6474
if (!reuseSession)
6575
{
6676
// session is either old or cannot communicate with the server
77+
Log.Warn("{0} Session{1} is unusable; destroying it", m_logArguments[0], session.Id);
6778
AdjustHostConnectionCount(session, -1);
6879
await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
6980
}
7081
else
7182
{
7283
// pooled session is ready to be used; return it
7384
session.OwningConnection = new WeakReference<MySqlConnection>(connection);
85+
int leasedSessionsCountPooled;
7486
lock (m_leasedSessions)
87+
{
7588
m_leasedSessions.Add(session.Id, session);
89+
leasedSessionsCountPooled = m_leasedSessions.Count;
90+
}
91+
if (Log.IsDebugEnabled())
92+
Log.Debug("{0} returning pooled Session{1} to caller; m_leasedSessions.Count={2}", m_logArguments[0], session.Id, leasedSessionsCountPooled);
7693
return session;
7794
}
7895
}
7996

8097
// create a new session
81-
session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastId));
98+
session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId));
99+
if (Log.IsInfoEnabled())
100+
Log.Info("{0} no pooled session available; created new Session{1}", m_logArguments[0], session.Id);
82101
await session.ConnectAsync(m_connectionSettings, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
83102
AdjustHostConnectionCount(session, 1);
84103
session.OwningConnection = new WeakReference<MySqlConnection>(connection);
104+
int leasedSessionsCountNew;
85105
lock (m_leasedSessions)
106+
{
86107
m_leasedSessions.Add(session.Id, session);
108+
leasedSessionsCountNew = m_leasedSessions.Count;
109+
}
110+
if (Log.IsDebugEnabled())
111+
Log.Debug("{0} returning new Session{1} to caller; m_leasedSessions.Count={2}", m_logArguments[0], session.Id, leasedSessionsCountNew);
87112
return session;
88113
}
89114
catch
@@ -110,6 +135,9 @@ private bool SessionIsHealthy(ServerSession session)
110135

111136
public void Return(ServerSession session)
112137
{
138+
if (Log.IsDebugEnabled())
139+
Log.Debug("{0} receiving Session{1} back", m_logArguments[0], session.Id);
140+
113141
try
114142
{
115143
lock (m_leasedSessions)
@@ -122,6 +150,7 @@ public void Return(ServerSession session)
122150
}
123151
else
124152
{
153+
Log.Warn("{0} received invalid Session{1}; destroying it", m_logArguments[0], session.Id);
125154
AdjustHostConnectionCount(session, -1);
126155
session.DisposeAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
127156
}
@@ -135,13 +164,15 @@ public void Return(ServerSession session)
135164
public async Task ClearAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
136165
{
137166
// increment the generation of the connection pool
167+
Log.Info("{0} clearing connection pool", m_logArguments);
138168
Interlocked.Increment(ref m_generation);
139169
RecoverLeakedSessions();
140170
await CleanPoolAsync(ioBehavior, session => session.PoolGeneration != m_generation, false, cancellationToken).ConfigureAwait(false);
141171
}
142172

143173
public async Task ReapAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
144174
{
175+
Log.Debug("{0} reaping connection pool", m_logArguments);
145176
RecoverLeakedSessions();
146177
if (m_connectionSettings.ConnectionIdleTimeout == 0)
147178
return;
@@ -165,6 +196,10 @@ private void RecoverLeakedSessions()
165196
recoveredSessions.Add(session);
166197
}
167198
}
199+
if (recoveredSessions.Count == 0)
200+
Log.Debug("{0} recovered no sessions", m_logArguments);
201+
else
202+
Log.Warn("{0} recovered {1} sessions", m_logArguments[0], recoveredSessions.Count);
168203
foreach (var session in recoveredSessions)
169204
session.ReturnToPool();
170205
}
@@ -218,6 +253,7 @@ private async Task CleanPoolAsync(IOBehavior ioBehavior, Func<ServerSession, boo
218253
if (shouldCleanFn(session))
219254
{
220255
// session should be cleaned; dispose it and keep iterating
256+
Log.Info("{0} found Session{1} to clean up", m_logArguments[0], session.Id);
221257
await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
222258
}
223259
else
@@ -266,7 +302,8 @@ private async Task CreateMinimumPooledSessions(IOBehavior ioBehavior, Cancellati
266302

267303
try
268304
{
269-
var session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastId));
305+
var session = new ServerSession(this, m_generation, Interlocked.Increment(ref m_lastSessionId));
306+
Log.Info("{0} created Session{1} to reach minimum pool size", m_logArguments[0], session.Id);
270307
await session.ConnectAsync(m_connectionSettings, m_loadBalancer, ioBehavior, cancellationToken).ConfigureAwait(false);
271308
AdjustHostConnectionCount(session, 1);
272309
lock (m_sessions)
@@ -343,7 +380,7 @@ private ConnectionPool(ConnectionSettings cs)
343380
m_cleanSemaphore = new SemaphoreSlim(1);
344381
m_sessionSemaphore = new SemaphoreSlim(cs.MaximumPoolSize);
345382
m_sessions = new LinkedList<ServerSession>();
346-
m_leasedSessions = new Dictionary<int, ServerSession>();
383+
m_leasedSessions = new Dictionary<string, ServerSession>();
347384
if (cs.LoadBalance == MySqlLoadBalance.LeastConnections)
348385
{
349386
m_hostSessions = new Dictionary<string, int>();
@@ -355,6 +392,14 @@ private ConnectionPool(ConnectionSettings cs)
355392
cs.LoadBalance == MySqlLoadBalance.Random ? RandomLoadBalancer.Instance :
356393
cs.LoadBalance == MySqlLoadBalance.LeastConnections ? new LeastConnectionsLoadBalancer(this) :
357394
(ILoadBalancer) new RoundRobinLoadBalancer();
395+
396+
Id = Interlocked.Increment(ref s_poolId);
397+
m_logArguments = new object[] { "Pool{0}".FormatInvariant(Id) };
398+
if (Log.IsInfoEnabled())
399+
{
400+
var csb = new MySqlConnectionStringBuilder(cs.ConnectionString);
401+
Log.Info("{0} creating new connection pool for {1}", m_logArguments[0], csb.GetConnectionString(includePassword: false));
402+
}
358403
}
359404

360405
private void AdjustHostConnectionCount(ServerSession session, int delta)
@@ -379,6 +424,7 @@ public IEnumerable<string> LoadBalance(IReadOnlyList<string> hosts)
379424
readonly ConnectionPool m_pool;
380425
}
381426

427+
static readonly IMySqlConnectorLogger Log = MySqlConnectorLogManager.CreateLogger(nameof(ConnectionPool));
382428
static readonly ReaderWriterLockSlim s_poolLock = new ReaderWriterLockSlim();
383429
static readonly Dictionary<string, ConnectionPool> s_pools = new Dictionary<string, ConnectionPool>();
384430
#if DEBUG
@@ -402,15 +448,18 @@ public IEnumerable<string> LoadBalance(IReadOnlyList<string> hosts)
402448
}
403449
});
404450

451+
static int s_poolId;
452+
405453
int m_generation;
406454
readonly SemaphoreSlim m_cleanSemaphore;
407455
readonly SemaphoreSlim m_sessionSemaphore;
408456
readonly LinkedList<ServerSession> m_sessions;
409457
readonly ConnectionSettings m_connectionSettings;
410-
readonly Dictionary<int, ServerSession> m_leasedSessions;
458+
readonly Dictionary<string, ServerSession> m_leasedSessions;
411459
readonly ILoadBalancer m_loadBalancer;
412460
readonly Dictionary<string, int> m_hostSessions;
413-
int m_lastId;
461+
readonly object[] m_logArguments;
414462
uint m_lastRecoveryTime;
463+
int m_lastSessionId;
415464
}
416465
}

0 commit comments

Comments
 (0)