Add optional `debugKey` parameter to SSE client (#67)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26bfc33..9a96173 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## 4.1.2-dev
 
 - Send `fetch` requests instead of `XHR` requests.
+- Add an optional `debugKey` parameter to `SseClient` to include in logging.
 
 ## 4.1.1
 
diff --git a/lib/client/sse_client.dart b/lib/client/sse_client.dart
index 15f85ed..abaea2e 100644
--- a/lib/client/sse_client.dart
+++ b/lib/client/sse_client.dart
@@ -26,6 +26,8 @@
 /// The client can send any JSON-encodable messages to the server by adding
 /// them to the [sink] and listen to messages from the server on the [stream].
 class SseClient extends StreamChannelMixin<String?> {
+  final String _clientId;
+
   final _incomingController = StreamController<String>();
 
   final _outgoingController = StreamController<String>();
@@ -43,12 +45,14 @@
   Timer? _errorTimer;
 
   /// [serverUrl] is the URL under which the server is listening for
-  /// incoming bi-directional SSE connections.
-  SseClient(String serverUrl) {
-    var clientId = generateUuidV4();
-    _eventSource =
-        EventSource('$serverUrl?sseClientId=$clientId', withCredentials: true);
-    _serverUrl = '$serverUrl?sseClientId=$clientId';
+  /// incoming bi-directional SSE connections. [debugKey] is an optional key
+  /// that can be used to identify the SSE connection.
+  SseClient(String serverUrl, {String? debugKey})
+      : _clientId = debugKey == null
+            ? generateUuidV4()
+            : '$debugKey-${generateUuidV4()}' {
+    _serverUrl = '$serverUrl?sseClientId=$_clientId';
+    _eventSource = EventSource(_serverUrl, withCredentials: true);
     _eventSource.onOpen.first.whenComplete(() {
       _onConnected.complete();
       _outgoingController.stream
@@ -113,7 +117,7 @@
     if (data == 'close') {
       close();
     } else {
-      throw UnsupportedError('Illegal Control Message "$data"');
+      throw UnsupportedError('[$_clientId] Illegal Control Message "$data"');
     }
   }
 
@@ -133,9 +137,9 @@
       try {
         encodedMessage = jsonEncode(message);
       } on JsonUnsupportedObjectError catch (e) {
-        _logger.warning('Unable to encode outgoing message: $e');
+        _logger.warning('[$_clientId] Unable to encode outgoing message: $e');
       } on ArgumentError catch (e) {
-        _logger.warning('Invalid argument: $e');
+        _logger.warning('[$_clientId] Invalid argument: $e');
       }
       try {
         final url = '$_serverUrl&messageId=${++_lastMessageId}';
@@ -147,7 +151,8 @@
                 credentialsOptions:
                     _CredentialsOptions(credentials: 'include')));
       } catch (error) {
-        final augmentedError = 'SSE client failed to send $message:\n $error';
+        final augmentedError =
+            '[$_clientId] SSE client failed to send $message:\n $error';
         _logger.severe(augmentedError);
         _closeWithError(augmentedError);
       }
diff --git a/lib/src/server/sse_handler.dart b/lib/src/server/sse_handler.dart
index 13ce670..88bb851 100644
--- a/lib/src/server/sse_handler.dart
+++ b/lib/src/server/sse_handler.dart
@@ -264,14 +264,15 @@
 
   Future<shelf.Response> _handleIncomingMessage(
       shelf.Request req, String path) async {
+    String? clientId;
     try {
-      var clientId = req.url.queryParameters['sseClientId'];
+      clientId = req.url.queryParameters['sseClientId'];
       var messageId = int.parse(req.url.queryParameters['messageId'] ?? '0');
       var message = await req.readAsString();
       var jsonObject = json.decode(message) as String;
       _connections[clientId]?._addIncomingMessage(messageId, jsonObject);
     } catch (e, st) {
-      _logger.fine('Failed to handle incoming message. $e $st');
+      _logger.fine('[$clientId] Failed to handle incoming message. $e $st');
     }
     return shelf.Response.ok('', headers: {
       'access-control-allow-credentials': 'true',