You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by fl...@apache.org on 2022/10/12 14:16:44 UTC

[tinkerpop] branch TINKERPOP-2471 created (now dff41f3f7f)

This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a change to branch TINKERPOP-2471
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


      at dff41f3f7f TINKERPOP-2471 Add logging to .NET

This branch includes the following new commits:

     new dff41f3f7f TINKERPOP-2471 Add logging to .NET

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[tinkerpop] 01/01: TINKERPOP-2471 Add logging to .NET

Posted by fl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a commit to branch TINKERPOP-2471
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit dff41f3f7f06fb7d539de7c577288e84bfb5d40f
Author: Florian Hockmann <fh...@florian-hockmann.de>
AuthorDate: Wed Oct 12 14:16:45 2022 +0200

    TINKERPOP-2471 Add logging to .NET
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/reference/gremlin-variants.asciidoc       | 11 ++++
 docs/src/upgrade/release-3.5.x.asciidoc            | 18 ++++++
 .../Gremlin.Net.Template.csproj                    |  3 +-
 gremlin-dotnet/src/Gremlin.Net.Template/Program.cs | 22 ++++---
 .../src/Gremlin.Net/Driver/ConnectionPool.cs       | 16 ++++-
 .../src/Gremlin.Net/Driver/GremlinClient.cs        | 23 +++++--
 .../src/Gremlin.Net/Driver/IGremlinClient.cs       |  1 -
 gremlin-dotnet/src/Gremlin.Net/Driver/Log.cs       | 46 ++++++++++++++
 .../Driver/Remote/DriverRemoteConnection.cs        | 71 ++++++++++++----------
 gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj  |  3 +-
 .../src/Gremlin.Net/Process/Traversal/Bytecode.cs  |  6 ++
 .../Gremlin.Net/Process/Traversal/Instruction.cs   |  2 +-
 gremlin-dotnet/src/pom.xml                         |  2 +-
 .../Docs/Reference/GremlinVariantsTests.cs         | 15 ++++-
 .../Gremlin.Net.IntegrationTest.csproj             |  1 +
 .../Driver/ConnectionPoolTests.cs                  | 12 ++--
 .../Process/Traversal/BytecodeTests.cs             | 11 ++++
 18 files changed, 200 insertions(+), 64 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 579c005883..6496cc4e67 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -34,6 +34,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Changed `JavaTranslator` exception handling so that an `IllegalArgumentException` is used for cases where the method exists but the signature can't be discerned given the arguments supplied.
 * Dockerized all test environment for .NET, JavaScript, Python, Go, and Python-based tests for Console, and added Docker as a build requirement.
 * Bumped to `snakeyaml` 1.32 to fix security vulnerability.
+* Added logging in .NET.
 
 ==== Bugs
 
diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc
index 60e5bdd406..2670e75777 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -1145,6 +1145,17 @@ attacks like CRIME/BREACH. Compression should therefore be turned off if the app
 server as well as data that could potentially be controlled by an untrusted user. Compression can be disabled via the
 `disableCompression` parameter.
 
+[[gremlin-dotnet-logging]]
+=== Logging
+
+It is possible to enable logging for the Gremlin.Net driver by providing an `ILoggerFactory` (from the
+`Microsoft.Extensions.Logging.Abstractions` package) to the `GremlinClient` constructor:
+
+[source,csharp]
+----
+include::../../../gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs[tags=logging]
+----
+
 [[gremlin-dotnet-serialization]]
 === Serialization
 
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 2b4f659ca0..2596d8beba 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -30,7 +30,25 @@ complete list of all the modifications that are part of this release.
 
 === Upgrading for Users
 
+==== Gremlin.NET: Add logging
 
+The Gremlin.NET driver can now be configured for logging to get more insights into its internal state, like when
+a connection was closed and will therefore be replaced. It uses `Microsoft.Extensions.Logging` for this so all kinds
+of different .NET logging implementations can be used.
+
+The following example shows how to provide a `LoggerFactory` that is configured to log to the console to
+`GremlinClient`:
+
+[source,csharp]
+----
+var loggerFactory = LoggerFactory.Create(builder =>
+{
+    builder.AddConsole();
+});
+var client = new GremlinClient(new GremlinServer("localhost", 8182), loggerFactory: loggerFactory);
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2471[TINKERPOP-2471]
 
 == TinkerPop 3.5.4
 
diff --git a/gremlin-dotnet/src/Gremlin.Net.Template/Gremlin.Net.Template.csproj b/gremlin-dotnet/src/Gremlin.Net.Template/Gremlin.Net.Template.csproj
index e7ba2b71fa..5ff2dc9fc2 100644
--- a/gremlin-dotnet/src/Gremlin.Net.Template/Gremlin.Net.Template.csproj
+++ b/gremlin-dotnet/src/Gremlin.Net.Template/Gremlin.Net.Template.csproj
@@ -22,7 +22,8 @@ limitations under the License.
     <TargetFramework>net6.0</TargetFramework>
   </PropertyGroup>
 
-  <ItemGroup>
+  <ItemGroup>    
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
     <!-- We need both reference elements until this is resolved: https://github.com/dotnet/sdk/issues/1151 -->
     <ProjectReference Include="../Gremlin.Net/Gremlin.Net.csproj" />
 
diff --git a/gremlin-dotnet/src/Gremlin.Net.Template/Program.cs b/gremlin-dotnet/src/Gremlin.Net.Template/Program.cs
index 93018e7e5a..42ffb9a255 100644
--- a/gremlin-dotnet/src/Gremlin.Net.Template/Program.cs
+++ b/gremlin-dotnet/src/Gremlin.Net.Template/Program.cs
@@ -24,8 +24,7 @@
 using System;
 using Gremlin.Net.Driver;
 using Gremlin.Net.Driver.Remote;
-using Gremlin.Net.Structure;
-
+using Microsoft.Extensions.Logging;
 using static Gremlin.Net.Process.Traversal.AnonymousTraversalSource;
 
 namespace Gremlin.Net.Template
@@ -37,15 +36,18 @@ namespace Gremlin.Net.Template
 
         private static void Main()
         {
-            using (var client = new GremlinClient(new GremlinServer(GremlinServerHostname, GremlinServerPort)))
+            var loggerFactory = LoggerFactory.Create(builder =>
+            {
+                builder.AddConsole();
+            });
+            using var connection = new DriverRemoteConnection(new GremlinClient(
+                new GremlinServer(GremlinServerHostname, GremlinServerPort), loggerFactory: loggerFactory));
+            var g = Traversal().WithRemote(connection);
+            var service = new Service(g);
+            var creators = service.FindCreatorsOfSoftware("lop");
+            foreach (var c in creators)
             {
-                var g = Traversal().WithRemote(new DriverRemoteConnection(client));
-                var service = new Service(g);
-                var creators = service.FindCreatorsOfSoftware("lop");
-                foreach (var c in creators)
-                {
-                    Console.WriteLine(c);
-                }
+                Console.WriteLine(c);
             }
         }
     }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionPool.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionPool.cs
index 8273561f53..e4f4a72ae2 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionPool.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/ConnectionPool.cs
@@ -28,6 +28,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Gremlin.Net.Driver.Exceptions;
 using Gremlin.Net.Process;
+using Microsoft.Extensions.Logging;
 using Polly;
 
 namespace Gremlin.Net.Driver
@@ -42,16 +43,19 @@ namespace Gremlin.Net.Driver
         private readonly ConcurrentDictionary<IConnection, byte> _deadConnections =
             new ConcurrentDictionary<IConnection, byte>();
         private readonly ConnectionPoolSettings _settings;
+        private readonly ILogger<ConnectionPool> _logger;
         private int _connectionIndex;
         private int _poolState;
         private const int PoolIdle = 0;
         private const int PoolPopulationInProgress = 1;
         private readonly CancellationTokenSource _cts = new CancellationTokenSource();
 
-        public ConnectionPool(IConnectionFactory connectionFactory, ConnectionPoolSettings settings)
+        public ConnectionPool(IConnectionFactory connectionFactory, ConnectionPoolSettings settings,
+            ILogger<ConnectionPool> logger)
         {
             _connectionFactory = connectionFactory;
             _settings = settings;
+            _logger = logger;
             ReplaceDeadConnectionsAsync().WaitUnwrap();
         }
         
@@ -60,7 +64,11 @@ namespace Gremlin.Net.Driver
         public IConnection GetAvailableConnection()
         {
             var connection = Policy.Handle<ServerUnavailableException>()
-                .WaitAndRetry(_settings.ReconnectionAttempts, ComputeRetrySleepDuration)
+                .WaitAndRetry(_settings.ReconnectionAttempts, ComputeRetrySleepDuration, onRetry:
+                    (_, _, retryCount, _) =>
+                    {
+                        _logger.CouldNotGetConnectionFromPool(retryCount, _settings.ReconnectionAttempts);
+                    })
                 .Execute(GetConnectionFromPool);
 
             return ProxiedConnection(connection);
@@ -116,10 +124,11 @@ namespace Gremlin.Net.Driver
 
             _deadConnections.Clear();
         }
-        
+
         private async Task FillPoolAsync()
         {
             var nrConnectionsToCreate = _settings.PoolSize - _connections.Count;
+            _logger.FillingPool(nrConnectionsToCreate);
             var connectionCreationTasks = new List<Task<IConnection>>(nrConnectionsToCreate);
             try
             {
@@ -218,6 +227,7 @@ namespace Gremlin.Net.Driver
         
         private void RemoveConnectionFromPool(IConnection connection)
         {
+            _logger.RemovingClosedConnectionFromPool();
             _deadConnections.TryAdd(connection, 0);
         }
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs
index 149622e78d..ed801ce4e4 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/GremlinClient.cs
@@ -27,6 +27,8 @@ using System.Threading.Tasks;
 using Gremlin.Net.Driver.Messages;
 using Gremlin.Net.Structure.IO;
 using Gremlin.Net.Structure.IO.GraphSON;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
 
 namespace Gremlin.Net.Driver
 {
@@ -36,6 +38,8 @@ namespace Gremlin.Net.Driver
     public class GremlinClient : IGremlinClient
     {
         private readonly ConnectionPool _connectionPool;
+        
+        internal ILoggerFactory LoggerFactory { get; }
 
         /// <summary>
         ///     Initializes a new instance of the <see cref="GremlinClient" /> class for the specified Gremlin Server.
@@ -118,8 +122,11 @@ namespace Gremlin.Net.Driver
                     connectionPoolSettings = new ConnectionPoolSettings {PoolSize = 1};
                 }
             }
-            _connectionPool =
-                new ConnectionPool(connectionFactory, connectionPoolSettings ?? new ConnectionPoolSettings());
+
+            LoggerFactory = NullLoggerFactory.Instance;
+
+            _connectionPool = new ConnectionPool(connectionFactory,
+                connectionPoolSettings ?? new ConnectionPoolSettings(), LoggerFactory.CreateLogger<ConnectionPool>());
         }
 
         private static void VerifyGraphSONArgumentTypeForMimeType<T>(object argument, string argumentName,
@@ -149,15 +156,16 @@ namespace Gremlin.Net.Driver
         /// <param name="disableCompression">
         ///     Whether to disable compression. Compression is only supported since .NET 6.
         ///     There it is also enabled by default.
-        ///
+        /// 
         ///     Note that compression might make your application susceptible to attacks like CRIME/BREACH. Compression
         ///     should therefore be turned off if your application sends sensitive data to the server as well as data
         ///     that could potentially be controlled by an untrusted user.
         /// </param>
+        /// <param name="loggerFactory">A factory to create loggers. If not provided, then nothing will be logged.</param>
         public GremlinClient(GremlinServer gremlinServer, IMessageSerializer messageSerializer = null,
             ConnectionPoolSettings connectionPoolSettings = null,
             Action<ClientWebSocketOptions> webSocketConfiguration = null, string sessionId = null,
-            bool disableCompression = false)
+            bool disableCompression = false, ILoggerFactory loggerFactory = null)
         {
             messageSerializer ??= new GraphSON3MessageSerializer();
             var webSocketSettings = new WebSocketSettings
@@ -184,8 +192,11 @@ namespace Gremlin.Net.Driver
                     connectionPoolSettings = new ConnectionPoolSettings {PoolSize = 1};
                 }
             }
-            _connectionPool =
-                new ConnectionPool(connectionFactory, connectionPoolSettings ?? new ConnectionPoolSettings());
+
+            LoggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
+
+            _connectionPool = new ConnectionPool(connectionFactory,
+                connectionPoolSettings ?? new ConnectionPoolSettings(), LoggerFactory.CreateLogger<ConnectionPool>());
         }
 
         /// <summary>
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/IGremlinClient.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/IGremlinClient.cs
index 9bef4be409..97baca25d5 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/IGremlinClient.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/IGremlinClient.cs
@@ -22,7 +22,6 @@
 #endregion
 
 using System;
-using System.Collections.Generic;
 using System.Threading.Tasks;
 using Gremlin.Net.Driver.Messages;
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Log.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Log.cs
new file mode 100644
index 0000000000..ae9c712f82
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Log.cs
@@ -0,0 +1,46 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#endregion
+
+using System;
+using Gremlin.Net.Process.Traversal;
+using Microsoft.Extensions.Logging;
+
+namespace Gremlin.Net.Driver
+{
+    internal static partial class Log
+    {
+        [LoggerMessage(20000, LogLevel.Information, "Creating {nrConnections} new connections")]
+        public static partial void FillingPool(this ILogger logger, int nrConnections);
+
+        [LoggerMessage(20001, LogLevel.Warning,
+            "Could not get a connection from the pool. Will try again. Retry {retryCount} of {maxRetries}")]
+        public static partial void CouldNotGetConnectionFromPool(this ILogger logger, int retryCount, int maxRetries);
+
+        [LoggerMessage(20002, LogLevel.Information,
+            "A connection was closed. Removing it from the pool so it can be replaced.")]
+        public static partial void RemovingClosedConnectionFromPool(this ILogger logger);
+        
+        [LoggerMessage(20003, LogLevel.Debug, "Submitting Bytecode {bytecode} for request: {requestId}")]
+        public static partial void SubmittingBytecode(this ILogger logger, Bytecode bytecode, Guid requestId);
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
index 2f32f778ba..761a85b74a 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
@@ -28,6 +28,8 @@ using Gremlin.Net.Driver.Messages;
 using Gremlin.Net.Process.Remote;
 using Gremlin.Net.Process.Traversal;
 using Gremlin.Net.Process.Traversal.Strategy.Decoration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
 
 namespace Gremlin.Net.Driver.Remote
 {
@@ -38,68 +40,69 @@ namespace Gremlin.Net.Driver.Remote
     {
         private readonly IGremlinClient _client;
         private readonly string _traversalSource;
-        
+        private readonly ILogger<DriverRemoteConnection> _logger;
+
         /// <summary>
         /// Filter on these keys provided to OptionsStrategy and apply them to the request. Note that
         /// "scriptEvaluationTimeout" was deprecated in 3.3.9 but still supported in server implementations and will
         /// be removed in later versions. 
         /// </summary>
-        private readonly List<String> _allowedKeys = new List<string> 
-                    {Tokens.ArgsEvalTimeout, "scriptEvaluationTimeout", Tokens.ArgsBatchSize, 
-                     Tokens.RequestId, Tokens.ArgsUserAgent};
+        private readonly List<string> _allowedKeys = new()
+        {
+            Tokens.ArgsEvalTimeout, "scriptEvaluationTimeout", Tokens.ArgsBatchSize,
+            Tokens.RequestId, Tokens.ArgsUserAgent
+        };
 
         private readonly string _sessionId;
         private string Processor => IsSessionBound ? Tokens.ProcessorSession : Tokens.ProcessorTraversal;
 
         /// <inheritdoc />
         public bool IsSessionBound => _sessionId != null;
-
-        /// <summary>
-        ///     Initializes a new <see cref="IRemoteConnection" /> using "g" as the default remote TraversalSource name.
-        /// </summary>
-        /// <param name="host">The host to connect to.</param>
-        /// <param name="port">The port to connect to.</param>
-        /// <exception cref="ArgumentNullException">Thrown when client is null.</exception>
-        public DriverRemoteConnection(string host, int port):this(host, port, "g")
-        {
-        }
-
+        
         /// <summary>
         ///     Initializes a new <see cref="IRemoteConnection" />.
         /// </summary>
         /// <param name="host">The host to connect to.</param>
         /// <param name="port">The port to connect to.</param>
         /// <param name="traversalSource">The name of the traversal source on the server to bind to.</param>
+        /// <param name="loggerFactory">A factory to create loggers. If not provided, then nothing will be logged.</param>
         /// <exception cref="ArgumentNullException">Thrown when client is null.</exception>
-        public DriverRemoteConnection(string host, int port, string traversalSource):this(new GremlinClient(new GremlinServer(host, port)), traversalSource)
+        public DriverRemoteConnection(string host, int port, string traversalSource = "g",
+            ILoggerFactory loggerFactory = null) : this(
+            new GremlinClient(new GremlinServer(host, port), loggerFactory: loggerFactory), traversalSource,
+            logger: loggerFactory != null
+                ? loggerFactory.CreateLogger<DriverRemoteConnection>()
+                : NullLogger<DriverRemoteConnection>.Instance)
         {
         }
 
         /// <summary>
-        ///     Initializes a new <see cref="IRemoteConnection" /> using "g" as the default remote TraversalSource name.
+        ///     Initializes a new <see cref="IRemoteConnection" />.
         /// </summary>
         /// <param name="client">The <see cref="IGremlinClient" /> that will be used for the connection.</param>
-        /// <exception cref="ArgumentNullException">Thrown when client is null.</exception>
-        public DriverRemoteConnection(IGremlinClient client):this(client, "g")
+        /// <param name="traversalSource">The name of the traversal source on the server to bind to.</param>
+        /// <exception cref="ArgumentNullException">Thrown when client or the traversalSource is null.</exception>
+        public DriverRemoteConnection(IGremlinClient client, string traversalSource = "g")
+            : this(client, traversalSource, null)
         {
         }
 
-        /// <summary>
-        ///     Initializes a new <see cref="IRemoteConnection" />.
-        /// </summary>
-        /// <param name="client">The <see cref="IGremlinClient" /> that will be used for the connection.</param>
-        /// <param name="traversalSource">The name of the traversal source on the server to bind to.</param>
-        /// <exception cref="ArgumentNullException">Thrown when client is null.</exception>
-        public DriverRemoteConnection(IGremlinClient client, string traversalSource)
+        private DriverRemoteConnection(IGremlinClient client, string traversalSource, string sessionId = null,
+            ILogger<DriverRemoteConnection> logger = null)
         {
             _client = client ?? throw new ArgumentNullException(nameof(client));
             _traversalSource = traversalSource ?? throw new ArgumentNullException(nameof(traversalSource));
-        }
 
-        private DriverRemoteConnection(IGremlinClient client, string traversalSource, Guid sessionId)
-            : this(client, traversalSource)
-        {
-            _sessionId = sessionId.ToString();
+            if (logger == null)
+            {
+                var loggerFactory = client is GremlinClient gremlinClient
+                    ? gremlinClient.LoggerFactory
+                    : NullLoggerFactory.Instance;
+
+                logger = loggerFactory.CreateLogger<DriverRemoteConnection>();
+            }
+            _logger = logger;
+            _sessionId = sessionId;
         }
 
         /// <summary>
@@ -116,6 +119,8 @@ namespace Gremlin.Net.Driver.Remote
 
         private async Task<IEnumerable<Traverser>> SubmitBytecodeAsync(Guid requestid, Bytecode bytecode)
         {
+            _logger.SubmittingBytecode(bytecode, requestid);
+            
             var requestMsg =
                 RequestMessage.Build(Tokens.OpsBytecode)
                     .Processor(Processor)
@@ -141,14 +146,14 @@ namespace Gremlin.Net.Driver.Remote
                     }
                 }
             }
-            
+
             return await _client.SubmitAsync<Traverser>(requestMsg.Create()).ConfigureAwait(false);
         }
 
         /// <inheritdoc />
         public RemoteTransaction Tx(GraphTraversalSource g)
         {
-            var session = new DriverRemoteConnection(_client, _traversalSource, Guid.NewGuid());
+            var session = new DriverRemoteConnection(_client, _traversalSource, Guid.NewGuid().ToString(), _logger);
             return new RemoteTransaction(session, g);
         }
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
index 9110d4de92..e16d088f73 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
+++ b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
@@ -21,7 +21,7 @@ limitations under the License.
     <TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
-    <LangVersion>8</LangVersion>
+    <LangVersion>9</LangVersion>
   </PropertyGroup>
 
   <PropertyGroup Label="Package">
@@ -71,6 +71,7 @@ NOTE that versions suffixed with "-rc" are considered release candidates (i.e. p
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
     <PackageReference Include="System.Text.Json" Version="6.0.6" />
     <PackageReference Include="Polly" Version="7.2.3" />
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
index cd53a1c6c5..e692ed23e2 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
@@ -168,5 +168,11 @@ namespace Gremlin.Net.Process.Traversal
         {
             return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>);
         }
+
+        /// <inheritdoc />
+        public override string ToString()
+        {
+            return $"[[{string.Join(", ", SourceInstructions)}], [{string.Join(", ", StepInstructions)}]]";
+        }
     }
 }
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs
index 1063b33749..b99343ab0b 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Instruction.cs
@@ -57,7 +57,7 @@ namespace Gremlin.Net.Process.Traversal
         /// </summary>
         public override string ToString()
         {
-            return OperatorName + " [" + String.Join(",", Arguments) + "]";
+            return $"{OperatorName}({string.Join(", ", Arguments)})";
         }
 
         /// <inheritdoc />
diff --git a/gremlin-dotnet/src/pom.xml b/gremlin-dotnet/src/pom.xml
index 264196baff..e5ed5985ce 100644
--- a/gremlin-dotnet/src/pom.xml
+++ b/gremlin-dotnet/src/pom.xml
@@ -61,7 +61,7 @@ limitations under the License.
                                     file.write(file.getText("UTF-8").replaceFirst(/&lt;Version&gt;(.*)&lt;\/Version&gt;/, "&lt;Version&gt;" + mavenVersion + "&lt;/Version&gt;"))
 
                                     file = new File(platformAgnosticBaseDirPath + "/Gremlin.Net.Template/Gremlin.Net.Template.csproj")
-                                    file.write(file.getText("UTF-8").replaceFirst(/Version="(.*)"/, "Version=\"" + mavenVersion + "\""))
+                                    file.write(file.getText("UTF-8").replaceFirst(/Gremlin\.Net\" Version="(.*)"/, "Gremlin.Net\" Version=\"" + mavenVersion + "\""))
 
                                     file = new File(platformAgnosticBaseDirPath + "/Gremlin.Net.Template/Gremlin.Net.Template.nuspec")
                                     file.write(file.getText("UTF-8").replaceFirst(/&lt;version&gt;(.*)&lt;\/version&gt;/, "&lt;version&gt;" + mavenVersion + "&lt;/version&gt;"))
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
index fb8479f223..4b277c4f8d 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Docs/Reference/GremlinVariantsTests.cs
@@ -32,6 +32,7 @@ using Gremlin.Net.Process.Traversal.Step.Util;
 using Gremlin.Net.Process.Traversal.Strategy.Decoration;
 using Gremlin.Net.Structure.IO.GraphBinary;
 using Gremlin.Net.Structure.IO.GraphSON;
+using Microsoft.Extensions.Logging;
 using Xunit;
 // tag::commonImports[]
 using static Gremlin.Net.Process.Traversal.AnonymousTraversalSource;
@@ -58,11 +59,23 @@ namespace Gremlin.Net.IntegrationTest.Docs.Reference
         public void ConnectingTest()
         {
 // tag::connecting[]
-var remoteConnection = new DriverRemoteConnection(new GremlinClient(new GremlinServer("localhost", 8182)), "g");
+using var remoteConnection = new DriverRemoteConnection(new GremlinClient(new GremlinServer("localhost", 8182)), "g");
 var g = Traversal().WithRemote(remoteConnection);
 // end::connecting[]
         }
         
+        [Fact(Skip="No Server under localhost")]
+        public void LoggingTest()
+        {
+// tag::logging[]
+            var loggerFactory = LoggerFactory.Create(builder =>
+            {
+                builder.AddConsole();
+            });
+            var client = new GremlinClient(new GremlinServer("localhost", 8182), loggerFactory: loggerFactory);
+// end::logging[]
+        }
+        
         [Fact(Skip="No Server under localhost")]
         public void SerializationGraphBinaryTest()
         {
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
index 697ae87856..ce3889cb8a 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
@@ -12,6 +12,7 @@
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="gherkin" Version="23.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
     <PackageReference Include="xunit" Version="2.4.2" />
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/ConnectionPoolTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/ConnectionPoolTests.cs
index 53e3506c22..9db8e91aa3 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/ConnectionPoolTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/ConnectionPoolTests.cs
@@ -27,6 +27,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using Gremlin.Net.Driver;
 using Gremlin.Net.Driver.Exceptions;
+using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
 using Xunit;
 
@@ -389,12 +390,11 @@ namespace Gremlin.Net.UnitTest.Driver
         private static ConnectionPool CreateConnectionPool(IConnectionFactory connectionFactory, int poolSize = 2,
             int reconnectionAttempts = 1)
         {
-            return new ConnectionPool(connectionFactory,
-                new ConnectionPoolSettings
-                {
-                    PoolSize = poolSize, ReconnectionAttempts = reconnectionAttempts,
-                    ReconnectionBaseDelay = TimeSpan.FromMilliseconds(10)   // let the tests execute fast
-                });
+            return new ConnectionPool(connectionFactory, new ConnectionPoolSettings
+            {
+                PoolSize = poolSize, ReconnectionAttempts = reconnectionAttempts,
+                ReconnectionBaseDelay = TimeSpan.FromMilliseconds(10) // let the tests execute fast
+            }, NullLogger<ConnectionPool>.Instance);
         }
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/BytecodeTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/BytecodeTests.cs
index 64f2f87c77..5f42b4e648 100644
--- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/BytecodeTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/BytecodeTests.cs
@@ -172,5 +172,16 @@ namespace Gremlin.Net.UnitTest.Process.Traversal
             var arg = bytecode.SourceInstructions[0].Arguments[0] as ISet<object>;
             Assert.Equal(new Binding("setVariable", "setValue"), arg.ToList()[1]);
         }
+
+        [Fact]
+        public void ShouldIncludeStepAndSourceInstructionsForToString()
+        {
+            var bytecode = new Bytecode();
+            bytecode.AddSource("source", 1, 2);
+            bytecode.AddStep("step1", 9, 8);
+            bytecode.AddStep("step2", 0);
+            
+            Assert.Equal("[[source(1, 2)], [step1(9, 8), step2(0)]]", bytecode.ToString());
+        }
     }
 }
\ No newline at end of file