-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add plain text and coloured logging to the Client
Logs websocket events (connect, message, error and close events)
- Loading branch information
1 parent
a0cf41b
commit 90ab1a6
Showing
19 changed files
with
742 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# 3. Logging methods vs logger class | ||
|
||
Date: 2024-03-19 | ||
|
||
## Status | ||
|
||
Accepted | ||
|
||
## Context | ||
|
||
I'm deciding between integrating logging directly into the main class or creating a dedicated logger class. | ||
|
||
### Option 1: Logging methods | ||
|
||
The first approach weaves logging actions into the operational code, resulting in a tight coupling of functionality and | ||
logging. Classes should be open for extension but closed for modification, and this strategy violates that principle. | ||
|
||
```ruby | ||
class Client | ||
def connect(relay) | ||
execute_within_an_em_thread do | ||
client = build_websocket_client(relay.url) | ||
parent_to_child_channel.subscribe do |msg| | ||
client.send(msg) | ||
emit(:send, msg) | ||
log_send(msg) # <------ new code | ||
end | ||
|
||
client.on :open do | ||
child_to_parent_channel.push(type: :open, relay:) | ||
log_connection_opened(relay) # <------ new code | ||
end | ||
|
||
client.on :message do |event| | ||
child_to_parent_channel.push(type: :message, data: event.data) | ||
log_message_received(event.data) # <------ new code | ||
end | ||
|
||
client.on :error do |event| | ||
child_to_parent_channel.push(type: :error, message: event.message) | ||
log_error(event.message) # <------ new code | ||
end | ||
|
||
client.on :close do |event| | ||
child_to_parent_channel.push(type: :close, code: event.code, reason: event.reason) | ||
log_connection_closed(event.code, event.reason) # <------ new code | ||
end | ||
end | ||
|
||
# ------ new code below ------ | ||
|
||
def log_send(msg) | ||
logger.info("Message sent: #{msg}") | ||
end | ||
|
||
def log_connection_opened(relay) | ||
logger.info("Connection opened to #{relay.url}") | ||
end | ||
|
||
def log_message_received(data) | ||
logger.info("Message received: #{data}") | ||
end | ||
|
||
def log_error(message) | ||
logger.error("Error: #{message}") | ||
end | ||
|
||
def log_connection_closed(code, reason) | ||
logger.info("Connection closed with code: #{code}, reason: #{reason}") | ||
end | ||
end | ||
end | ||
``` | ||
|
||
### Option 2: Logger class | ||
|
||
The second strategy separates logging into its own class, promoting cleaner code and adherence to the Single | ||
Responsibility Principle. Client already exposes events that can be tapped into, so the logger class can listen to these | ||
events and log accordingly. | ||
|
||
```ruby | ||
class ClientLogger | ||
def attach_to(client) | ||
logger_instance = self | ||
|
||
client.on(:connect) { |relay| logger_instance.on_connect(relay) } | ||
client.on(:message) { |message| logger_instance.on_message(message) } | ||
client.on(:send) { |message| logger_instance.on_send(message) } | ||
client.on(:error) { |message| logger_instance.on_error(message) } | ||
client.on(:close) { |code, reason| logger_instance.on_close(code, reason) } | ||
end | ||
|
||
def on_connect(relay); end | ||
def on_message(message); end | ||
def on_send(message); end | ||
def on_error(message); end | ||
def on_close(code, reason); end | ||
end | ||
|
||
client = Nostr::Client.new | ||
logger = Nostr::ClientLogger.new | ||
logger.attach_to(client) | ||
``` | ||
This approach decouples logging from the main class, making it easier to maintain and extend the logging system without | ||
affecting the core logic. | ||
## Decision | ||
I've chosen the dedicated logger class route. This choice is driven by a desire for extensibility in the logging system. | ||
With a separate logger, I can easily modify logging behavior—like changing formats, adjusting verbosity levels, | ||
switching colors, or altering output destinations (files, networks, etc.) — without needing to rewrite any of the main | ||
operational code. | ||
## Consequences | ||
Adopting a dedicated logger class offers greater flexibility and simplifies maintenance, making it straightforward to | ||
adjust how and what I log independently of the core logic. This separation of concerns means that any future changes | ||
to logging preferences or requirements can be implemented quickly and without risk to the main class's functionality. | ||
However, it's important to manage the integration carefully to avoid introducing complexity, such as handling | ||
dependencies and ensuring seamless communication between the main operations and the logging system. | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# 3. Default Logging Activation | ||
|
||
Date: 2024-03-19 | ||
|
||
## Status | ||
|
||
Accepted | ||
|
||
## Context | ||
|
||
Logging provides visibility into the gem's behavior and helps to diagnose issues. The decision centered on whether | ||
to enable logging by default or require manual activation. | ||
|
||
### Option 1: On-demand Logging | ||
|
||
```ruby | ||
client = Nostr::Client.new | ||
logger = Nostr::ClientLogger.new | ||
logger.attach_to(client) | ||
``` | ||
|
||
#### Advantages: | ||
|
||
- Offers users flexibility and control over logging. | ||
- Conserves resources by logging only when needed. | ||
|
||
#### Disadvantages: | ||
|
||
- Potential to miss critical logs if not enabled. | ||
- Requires users to read and understand documentation to enable logging. | ||
- Requires users to manually activate and configure logging. | ||
|
||
### Option 2: Automatic Logging | ||
|
||
```ruby | ||
class Client | ||
def initialize(logger: ClientLogger.new) | ||
@logger = logger | ||
logger&.attach_to(self) | ||
end | ||
end | ||
|
||
client = Nostr::Client.new | ||
``` | ||
|
||
#### Advantages: | ||
|
||
- Ensures comprehensive logging without user intervention. | ||
- Simplifies debugging and monitoring. | ||
- Balances logging detail with performance impact. | ||
|
||
#### Disadvantages: | ||
|
||
- Needs additional steps to disable logging. | ||
|
||
## Decision | ||
|
||
Logging will be enabled by default. Users can disable logging by passing a `nil` logger to the client: | ||
|
||
```ruby | ||
client = Nostr::Client.new(logger: nil) | ||
``` | ||
|
||
## Consequences | ||
|
||
Enabling logging by default favors ease of use and simplifies the developer's experience. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# 3. Logger Types | ||
|
||
Date: 2024-04-01 | ||
|
||
## Status | ||
|
||
Accepted | ||
|
||
## Context | ||
|
||
In developing the `Nostr::Client` logging mechanism, I identified a need to cater to multiple development environments | ||
and developer preferences. The consideration was whether to implement a singular logger type or to introduce | ||
multiple, specialized loggers. The alternatives were: | ||
|
||
- A single `ClientLogger` providing a one-size-fits-all solution. | ||
- Multiple logger types: | ||
- `Nostr::Client::Logger`: The base class for logging, providing core functionalities. | ||
- `Nostr::Client::ColorLogger`: An extension of the base logger, introducing color-coded outputs for enhanced readability in environments that support ANSI colors. | ||
- `Nostr::Client::PlainLogger`: A variation that produces logs without color coding, suitable for environments lacking color support or for users preferring plain text. | ||
|
||
## Decision | ||
|
||
I decided to implement the latter option: three specific kinds of loggers (`Nostr::Client::Logger`, | ||
`Nostr::Client::ColorLogger`, and `Nostr::Client::PlainLogger`). This approach is intended to offer flexibility and | ||
cater to the varied preferences and requirements of our users, recognizing the diverse environments in which our | ||
library might be used. | ||
|
||
## Consequences | ||
|
||
- `Developer Choice`: Developers gain the ability to select the one that best matches their needs and environmental constraints, thereby enhancing the library's usability. | ||
- `Code Complexity`: While introducing multiple logger types increases the library's code complexity, this is offset by the significant gain in flexibility and user satisfaction. | ||
- `Broad Compatibility`: This decision ensures that the logging mechanism is adaptable to a wide range of operational environments, enhancing the library's overall robustness and accessibility. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Logging and debugging | ||
|
||
The `Nostr::Client` class provides built-in logging functionality to help you debug and monitor client interactions with | ||
relays. By default, the client uses the `ColorLogger`, which logs events in color. However, you can customize the | ||
logging behavior or disable it entirely. | ||
|
||
## Disabling logging | ||
|
||
To instantiate a client without any logging, simply pass `logger: nil` when creating the client instance: | ||
|
||
```ruby | ||
client = Nostr::Client.new(logger: nil) | ||
``` | ||
|
||
This will disable all logging for the client. | ||
|
||
## Formatting the logging | ||
|
||
The `Nostr::Client::Logger` class is the base class for logging functionality. It defines the following methods for | ||
logging different events: | ||
|
||
- `on_connect(relay)`: Logs when the client connects to a relay. | ||
- `on_message(message)`: Logs a message received from the relay. | ||
- `on_send(message)`: Logs a message sent to the relay. | ||
- `on_error(message)`: Logs an error message. | ||
- `on_close(code, reason)`: Logs when the connection with a relay is closed. | ||
|
||
You can create your own logger by subclassing `Nostr::Client::Logger` and overriding these methods to customize the | ||
logging format. | ||
|
||
The `Nostr::Client::ColorLogger` is a built-in logger that logs events in color. It uses ANSI escape codes to add color | ||
to the log output. Here's an example of how the ColorLogger formats the log messages: | ||
|
||
- Connection: `"\u001b[32m\u001b[1mConnected to the relay\u001b[22m #{relay.name} (#{relay.url})\u001b[0m"` | ||
- Message received: `"\u001b[32m\u001b[1m◄-\u001b[0m #{message}"` | ||
- Message sent: `"\u001b[32m\u001b[1m-►\u001b[0m #{message}"` | ||
- Error: `"\u001b[31m\u001b[1mError: \u001b[22m#{message}\u001b[0m"` | ||
- Connection closed: `"\u001b[31m\u001b[1mConnection closed: \u001b[22m#{reason} (##{code})\u001b[0m"` | ||
|
||
## Plain text logging | ||
|
||
If you prefer plain text logging without colors, you can use the `Nostr::Client::PlainLogger`. This logger formats the | ||
log messages in a simple, readable format without any ANSI escape codes. | ||
|
||
To use the `PlainLogger`, pass it as the `logger` option when creating the client instance: | ||
|
||
```ruby | ||
client = Nostr::Client.new(logger: Nostr::Client::PlainLogger.new) | ||
``` | ||
|
||
The `PlainLogger` formats the log messages as follows: | ||
|
||
- Connection: `"Connected to the relay #{relay.name} (#{relay.url})"` | ||
- Message received: `"◄- #{message}"` | ||
- Message sent: `"-► #{message}"` | ||
- Error: `"Error: #{message}"` | ||
- Connection closed: `"Connection closed: #{reason} (##{code})"` | ||
|
||
By using the appropriate logger or creating your own custom logger, you can effectively debug and monitor your Nostr | ||
client's interactions with relays. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.