From dc4b3f89bed115b7e2d499a457ae8fdd79d40e8b Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 20:36:54 -0400 Subject: [PATCH 01/11] Export `logger.Level` --- lib/src/log.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/log.dart b/lib/src/log.dart index aec896b5..31fe429b 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -1,6 +1,8 @@ import "dart:io"; import "package:logger/logger.dart"; +export "package:logger/logger.dart"; + /// A filter to decide which messages get logged. Set [LogFilter.level] to change. final logFilter = ProductionFilter(); /// The logger to use when running BURT programs. See [LoggerUtils] for usage. From f84a1ae715ebeb398fc4acf75f6ef3dac138d08c Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 20:45:43 -0400 Subject: [PATCH 02/11] Fixed logFilter --- lib/src/log.dart | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/src/log.dart b/lib/src/log.dart index 31fe429b..99985c45 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -3,10 +3,30 @@ import "package:logger/logger.dart"; export "package:logger/logger.dart"; -/// A filter to decide which messages get logged. Set [LogFilter.level] to change. -final logFilter = ProductionFilter(); +/// An alias for [Level]. +typedef LogLevel = Level; + +/// Only logs messages at the given [level]. Set [level] to change at any time. +class BurtFilter implements LogFilter { + LogLevel _level; + @override LogLevel get level => _level; + @override set level(LogLevel? value) => _level = value ?? LogLevel.info; + + /// Creates a [LogFilter] at the given level. Set [level] to change. + BurtFilter([this._level = LogLevel.info]); + + @override + bool shouldLog(LogEvent event) => event.level.index >= level.index; + + @override + void init() { } + + @override + void destroy() { } +} + /// The logger to use when running BURT programs. See [LoggerUtils] for usage. -Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: logFilter); +Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: BurtFilter()); /// Helpful aliases for the [Logger] class. extension LoggerUtils on Logger { From 769e54769812a40966bb4f4e841f7288fd84a299 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 20:46:52 -0400 Subject: [PATCH 03/11] Exposed BurtFilter --- lib/src/log.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/log.dart b/lib/src/log.dart index 99985c45..c9ef8902 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -7,13 +7,13 @@ export "package:logger/logger.dart"; typedef LogLevel = Level; /// Only logs messages at the given [level]. Set [level] to change at any time. -class BurtFilter implements LogFilter { +class BurtLogFilter implements LogFilter { LogLevel _level; @override LogLevel get level => _level; @override set level(LogLevel? value) => _level = value ?? LogLevel.info; /// Creates a [LogFilter] at the given level. Set [level] to change. - BurtFilter([this._level = LogLevel.info]); + BurtLogFilter([this._level = LogLevel.info]); @override bool shouldLog(LogEvent event) => event.level.index >= level.index; @@ -25,8 +25,10 @@ class BurtFilter implements LogFilter { void destroy() { } } +/// A filter to decide which messages get logged. Set [LogFilter.level] to change. +final logFilter = BurtLogFilter(); /// The logger to use when running BURT programs. See [LoggerUtils] for usage. -Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: BurtFilter()); +Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: logFilter); /// Helpful aliases for the [Logger] class. extension LoggerUtils on Logger { From c2f82d53a3e0c7f9ecc5fd02f12b575d6323560b Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 21:01:51 -0400 Subject: [PATCH 04/11] Fixed logfilter issues --- analysis_options.yaml | 1 - example/raw_data.dart | 20 ++++++++++++++++++++ lib/src/log.dart | 5 ++++- test/proto_test.dart | 3 +-- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 example/raw_data.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index c23adc72..afc8ba0c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -27,7 +27,6 @@ analyzer: exclude: - lib/src/generated/**.dart - - example/**.dart linter: rules: diff --git a/example/raw_data.dart b/example/raw_data.dart new file mode 100644 index 00000000..fae75f40 --- /dev/null +++ b/example/raw_data.dart @@ -0,0 +1,20 @@ +import "dart:io"; +import "package:burt_network/burt_network.dart"; + +class TestSocket extends UdpSocket { + TestSocket({required super.port}); + + @override + void onData(Datagram packet) => logger.info("Received data: ${packet.data} from ${packet.port}"); +} + +void main() async { + logFilter.level = LogLevel.info; + final socket1 = TestSocket(port: 8001); + final socket2 = TestSocket(port: 8002); + + await socket1.init(); + await socket2.init(); + + socket1.sendData([1, 2, 3], SocketInfo(address: InternetAddress.loopbackIPv4, port: 8002)); +} diff --git a/lib/src/log.dart b/lib/src/log.dart index c9ef8902..e4c85aac 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -10,7 +10,10 @@ typedef LogLevel = Level; class BurtLogFilter implements LogFilter { LogLevel _level; @override LogLevel get level => _level; - @override set level(LogLevel? value) => _level = value ?? LogLevel.info; + @override set level(LogLevel? value) { + Logger.level = value ?? LogLevel.info; + _level = value ?? LogLevel.info; + } /// Creates a [LogFilter] at the given level. Set [level] to change. BurtLogFilter([this._level = LogLevel.info]); diff --git a/test/proto_test.dart b/test/proto_test.dart index 34e1e600..7f6ab38a 100644 --- a/test/proto_test.dart +++ b/test/proto_test.dart @@ -1,6 +1,5 @@ import "dart:io"; import "package:burt_network/burt_network.dart"; -import "package:burt_network/generated.dart"; import "package:test/test.dart"; final address = InternetAddress.loopbackIPv4; @@ -77,7 +76,7 @@ class TestServer extends ServerSocket { } class TestClient extends ProtoSocket { - TestClient({required super.port, required super.device, super.destination}); + TestClient({required super.port, required super.device, super.destination}) : super(heartbeatInterval: const Duration(seconds: 1)); bool isConnected = false; bool shouldSendHeartbeats = true; From 98da2c806d59c7651b7ccdb9e4ba223d064671dd Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 21:14:22 -0400 Subject: [PATCH 05/11] Fixed logger issues, for real this time --- example/raw_data.dart | 3 ++- lib/src/log.dart | 26 +------------------------- lib/src/udp_socket.dart | 9 +++++---- test/proto_test.dart | 1 + 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/example/raw_data.dart b/example/raw_data.dart index fae75f40..266ea594 100644 --- a/example/raw_data.dart +++ b/example/raw_data.dart @@ -9,7 +9,8 @@ class TestSocket extends UdpSocket { } void main() async { - logFilter.level = LogLevel.info; + Logger.level = LogLevel.warning; + logger.warning("This should be seen"); final socket1 = TestSocket(port: 8001); final socket2 = TestSocket(port: 8002); diff --git a/lib/src/log.dart b/lib/src/log.dart index e4c85aac..b0fd76c6 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -6,32 +6,8 @@ export "package:logger/logger.dart"; /// An alias for [Level]. typedef LogLevel = Level; -/// Only logs messages at the given [level]. Set [level] to change at any time. -class BurtLogFilter implements LogFilter { - LogLevel _level; - @override LogLevel get level => _level; - @override set level(LogLevel? value) { - Logger.level = value ?? LogLevel.info; - _level = value ?? LogLevel.info; - } - - /// Creates a [LogFilter] at the given level. Set [level] to change. - BurtLogFilter([this._level = LogLevel.info]); - - @override - bool shouldLog(LogEvent event) => event.level.index >= level.index; - - @override - void init() { } - - @override - void destroy() { } -} - -/// A filter to decide which messages get logged. Set [LogFilter.level] to change. -final logFilter = BurtLogFilter(); /// The logger to use when running BURT programs. See [LoggerUtils] for usage. -Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: logFilter); +Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: ProductionFilter()); /// Helpful aliases for the [Logger] class. extension LoggerUtils on Logger { diff --git a/lib/src/udp_socket.dart b/lib/src/udp_socket.dart index 4bcba4c8..3ecc95e9 100644 --- a/lib/src/udp_socket.dart +++ b/lib/src/udp_socket.dart @@ -18,7 +18,7 @@ import "log.dart"; /// - Call [dispose] to close the socket. Messages can no longer be sent or received after this. abstract class UdpSocket { /// The port this socket is listening on. See [RawDatagramSocket.bind]. - final int port; + int? port; /// Opens a UDP socket on the given port that can send and receive data. UdpSocket({required this.port}); @@ -36,17 +36,18 @@ abstract class UdpSocket { /// Initializes the socket. @mustCallSuper Future init() async { - logger.verbose("Listening on port $port"); - _socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port); + _socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port ?? 0); _subscription = _socket.listenForData(onData); + if (port == null || port == 0) port = _socket.port; + logger.info("Listening on port $port"); } /// Closes the socket. @mustCallSuper Future dispose() async { - logger.verbose("Closed the socket on port $port"); await _subscription.cancel(); _socket.close(); + logger.info("Closed the socket on port $port}"); } /// Sends data to the given destination. diff --git a/test/proto_test.dart b/test/proto_test.dart index 7f6ab38a..dff97206 100644 --- a/test/proto_test.dart +++ b/test/proto_test.dart @@ -8,6 +8,7 @@ final serverInfo = SocketInfo(address: address, port: 8000); final clientInfo = SocketInfo(address: address, port: 8001); void main() => group("ProtoSocket:", () { + Logger.level = LogLevel.debug; final server = TestServer(port: serverInfo.port, device: Device.SUBSYSTEMS); final client = TestClient( device: Device.DASHBOARD, From f6126d3375b7a0df212bf7133e071474f3e9d18c Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 21:51:52 -0400 Subject: [PATCH 06/11] Upgraded `UdpSocket` with error handling. Added example --- example/server.dart | 16 ++++++++++++++++ lib/burt_network.dart | 10 +++++----- lib/src/udp_socket.dart | 33 +++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 example/server.dart diff --git a/example/server.dart b/example/server.dart new file mode 100644 index 00000000..f978638e --- /dev/null +++ b/example/server.dart @@ -0,0 +1,16 @@ +import "package:burt_network/burt_network.dart"; + +class BasicServer extends ServerSocket { + BasicServer({required super.port, required super.device}); + + @override + void onMessage(WrappedMessage wrapper) => logger.info("Received ${wrapper.name} message: ${wrapper.data}"); +} + +void main() async { + Logger.level = LogLevel.info; + final server = BasicServer(port: 8001, device: Device.SUBSYSTEMS); // Registers as the Subsystems Server on the Dashboard + final server2 = BasicServer(port: 8002, device: Device.VIDEO); // Registers as the Subsystems Server on the Dashboard + await server.init(); + await server2.init(); +} diff --git a/lib/burt_network.dart b/lib/burt_network.dart index 4da8ff45..66e8701d 100644 --- a/lib/burt_network.dart +++ b/lib/burt_network.dart @@ -15,14 +15,14 @@ /// not have to use [UdpSocket] directly as it has no Protobuf support. library; -export "src/server_socket.dart"; +import "src/proto_socket.dart"; +import "src/server_socket.dart"; +import "src/udp_socket.dart"; + export "src/log.dart"; export "src/proto_socket.dart"; +export "src/server_socket.dart"; export "src/socket_info.dart"; export "src/udp_socket.dart"; export "generated.dart"; - -import "src/server_socket.dart"; -import "src/proto_socket.dart"; -import "src/udp_socket.dart"; diff --git a/lib/src/udp_socket.dart b/lib/src/udp_socket.dart index 3ecc95e9..1a3bce26 100644 --- a/lib/src/udp_socket.dart +++ b/lib/src/udp_socket.dart @@ -17,6 +17,9 @@ import "log.dart"; /// - Override [onData] to handle incoming data. /// - Call [dispose] to close the socket. Messages can no longer be sent or received after this. abstract class UdpSocket { + /// A collection of allowed [OSError] codes. + static const allowedErrors = {1234, 10054, 101, 10038, 9}; + /// The port this socket is listening on. See [RawDatagramSocket.bind]. int? port; @@ -33,21 +36,35 @@ abstract class UdpSocket { /// This must be cancelled in [dispose]. late StreamSubscription _subscription; - /// Initializes the socket. + /// Initializes the socket, and restarts it if a known "safe" error occurs (see [allowedErrors]). @mustCallSuper - Future init() async { - _socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port ?? 0); - _subscription = _socket.listenForData(onData); - if (port == null || port == 0) port = _socket.port; - logger.info("Listening on port $port"); - } + Future init() async => runZonedGuarded>( + // This code cannot be a try/catch because the SocketException can be thrown at any time, + // even after this function has finished. It also cannot be caught by the caller of this function. + // Using [runZonedGuarded] ensures that the error is caught no matter when it is thrown. + () async { // Initialize the socket + _socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port ?? 0); + _subscription = _socket.listenForData(onData); + if (port == null || port == 0) port = _socket.port; + logger.info("Listening on port $port"); + }, + (Object error, StackTrace stack) async { // Catch errors and restart the socket + if (error is SocketException && allowedErrors.contains(error.osError!.errorCode)) { + logger.warning("Socket error ${error.osError!.errorCode} on port $port. Restarting..."); + await dispose(); + await init(); + } else { + Error.throwWithStackTrace(error, stack); + } + } + ); /// Closes the socket. @mustCallSuper Future dispose() async { await _subscription.cancel(); _socket.close(); - logger.info("Closed the socket on port $port}"); + logger.info("Closed the socket on port $port"); } /// Sends data to the given destination. From 58c6127664668bcec16ec0f8888b1ed33dfaa52d Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 22:09:07 -0400 Subject: [PATCH 07/11] Added `ProtoSocket.sendWrapper` --- lib/src/proto_socket.dart | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/src/proto_socket.dart b/lib/src/proto_socket.dart index 23c5b21a..17d019f5 100644 --- a/lib/src/proto_socket.dart +++ b/lib/src/proto_socket.dart @@ -6,7 +6,6 @@ import "package:burt_network/generated.dart"; import "udp_socket.dart"; import "socket_info.dart"; -import "log.dart"; /// A [UdpSocket] to send and receive Protobuf messages. /// @@ -87,13 +86,21 @@ abstract class ProtoSocket extends UdpSocket { } /// Wraps a [Message] and sends it to the [destination], or the given [socketOverride] if specified. + /// + /// If you have already wrapped a message yourself, use [sendWrapper]. void sendMessage(Message message, {SocketInfo? socketOverride}) { final wrapper = message.wrap(); final target = socketOverride ?? destination; - if (target == null) { - logger.critical("No destination or override was specificed"); - throw ArgumentError.notNull("socketOverride"); - } + if (target == null) return; + sendData(wrapper.writeToBuffer(), target); + } + + /// Sends an already-wrapped [WrappedMessage] to the [destination], or the given [socketOverride]. + /// + /// Use this function instead of [sendMessage] if you need to manually wrap a message yourself. + void sendWrapper(WrappedMessage wrapper, {SocketInfo? socketOverride}) { + final target = socketOverride ?? destination; + if (target == null) return; sendData(wrapper.writeToBuffer(), target); } From fc466a3f274c8a66fd544b0604a7adaf7e4c7c4c Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Sun, 23 Jul 2023 22:22:50 -0400 Subject: [PATCH 08/11] Removed heartbeat log --- lib/src/server_socket.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/server_socket.dart b/lib/src/server_socket.dart index 15f80f78..bfa91604 100644 --- a/lib/src/server_socket.dart +++ b/lib/src/server_socket.dart @@ -72,7 +72,6 @@ abstract class ServerSocket extends ProtoSocket { /// 4. If we are not connected to any dashboard, call [onConnect] and respond to it. @override void onHeartbeat(Connect heartbeat, SocketInfo source) { - logger.debug("Received heartbeat from $source"); if (heartbeat.receiver != device) { // (1) logger.warning("Received a misaddressed heartbeat for ${heartbeat.receiver}"); } else if (isConnected) { From 9f31960470e11973468924a558328cf93533f3df Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 25 Jul 2023 14:29:12 -0400 Subject: [PATCH 09/11] Fix logger issues --- example/raw_data.dart | 2 -- example/server.dart | 1 - lib/src/log.dart | 13 ++++++++++--- test/proto_test.dart | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/example/raw_data.dart b/example/raw_data.dart index 266ea594..65cd2b77 100644 --- a/example/raw_data.dart +++ b/example/raw_data.dart @@ -9,8 +9,6 @@ class TestSocket extends UdpSocket { } void main() async { - Logger.level = LogLevel.warning; - logger.warning("This should be seen"); final socket1 = TestSocket(port: 8001); final socket2 = TestSocket(port: 8002); diff --git a/example/server.dart b/example/server.dart index f978638e..bcd99e61 100644 --- a/example/server.dart +++ b/example/server.dart @@ -8,7 +8,6 @@ class BasicServer extends ServerSocket { } void main() async { - Logger.level = LogLevel.info; final server = BasicServer(port: 8001, device: Device.SUBSYSTEMS); // Registers as the Subsystems Server on the Dashboard final server2 = BasicServer(port: 8002, device: Device.VIDEO); // Registers as the Subsystems Server on the Dashboard await server.init(); diff --git a/lib/src/log.dart b/lib/src/log.dart index b0fd76c6..cce9d26b 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -1,13 +1,20 @@ import "dart:io"; import "package:logger/logger.dart"; -export "package:logger/logger.dart"; - /// An alias for [Level]. typedef LogLevel = Level; +class BurtLogger { + static LogLevel level = LogLevel.info; +} + +class BurtFilter extends LogFilter { + @override + bool shouldLog(LogEvent event) => event.level.index >= BurtLogger.level.index; +} + /// The logger to use when running BURT programs. See [LoggerUtils] for usage. -Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: ProductionFilter()); +Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: BurtFilter()); /// Helpful aliases for the [Logger] class. extension LoggerUtils on Logger { diff --git a/test/proto_test.dart b/test/proto_test.dart index dff97206..46361a5d 100644 --- a/test/proto_test.dart +++ b/test/proto_test.dart @@ -8,7 +8,7 @@ final serverInfo = SocketInfo(address: address, port: 8000); final clientInfo = SocketInfo(address: address, port: 8001); void main() => group("ProtoSocket:", () { - Logger.level = LogLevel.debug; + BurtLogger.level = LogLevel.debug; final server = TestServer(port: serverInfo.port, device: Device.SUBSYSTEMS); final client = TestClient( device: Device.DASHBOARD, From d32117dce310739b118c355bd3151029701a89c8 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 25 Jul 2023 14:38:48 -0400 Subject: [PATCH 10/11] Documented --- lib/src/log.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/log.dart b/lib/src/log.dart index cce9d26b..0f4fb6fc 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -4,17 +4,22 @@ import "package:logger/logger.dart"; /// An alias for [Level]. typedef LogLevel = Level; +/// Holds the current [LogLevel] for [logger]. class BurtLogger { + /// The current [LogLevel] for [logger]. static LogLevel level = LogLevel.info; } +/// A custom filter to work around a bug with `package:logger`. +/// +/// See https://github.com/Bungeefan/logger/issues/38. class BurtFilter extends LogFilter { @override bool shouldLog(LogEvent event) => event.level.index >= BurtLogger.level.index; } /// The logger to use when running BURT programs. See [LoggerUtils] for usage. -Logger logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: BurtFilter()); +final logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: BurtFilter()); /// Helpful aliases for the [Logger] class. extension LoggerUtils on Logger { From dd1e615f3e3572c2b3cfacefe921acac7b028c13 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 25 Jul 2023 14:57:24 -0400 Subject: [PATCH 11/11] Documented --- lib/src/log.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/log.dart b/lib/src/log.dart index 0f4fb6fc..2967f123 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -18,7 +18,9 @@ class BurtFilter extends LogFilter { bool shouldLog(LogEvent event) => event.level.index >= BurtLogger.level.index; } -/// The logger to use when running BURT programs. See [LoggerUtils] for usage. +/// The logger to use when running BURT programs. +/// +/// See [LoggerUtils] for usage. To change the minimum log level, use [BurtLogger.level]. final logger = Logger(printer: SimplePrinter(colors: stdout.supportsAnsiEscapes), filter: BurtFilter()); /// Helpful aliases for the [Logger] class.