Delegate nostr_core_lib logging via callback and integrate single-file didactyl logging
This commit is contained in:
@@ -457,8 +457,8 @@ ldd your_app # Shows linked system libraries
|
||||
### Build Flags
|
||||
|
||||
```bash
|
||||
# Enable all logging
|
||||
make LOGGING_FLAGS="-DENABLE_FILE_LOGGING -DENABLE_WEBSOCKET_LOGGING -DENABLE_DEBUG_LOGGING"
|
||||
# Enable websocket and PoW debug emission (now callback-based)
|
||||
make LOGGING_FLAGS="-DENABLE_WEBSOCKET_LOGGING -DENABLE_DEBUG_LOGGING"
|
||||
|
||||
# Debug build
|
||||
make debug
|
||||
@@ -467,6 +467,40 @@ make debug
|
||||
make arm64
|
||||
```
|
||||
|
||||
### Logging Integration (Consumer-Controlled)
|
||||
|
||||
`nostr_core_lib` now supports a callback logging API so host applications can route library logs into their own logger and destination.
|
||||
|
||||
- API is exposed via [`nostr_core/nostr_log.h`](nostr_core/nostr_log.h)
|
||||
- Included automatically from [`nostr_core/nostr_core.h`](nostr_core/nostr_core.h)
|
||||
|
||||
```c
|
||||
#include "nostr_core/nostr_core.h"
|
||||
|
||||
static void my_log_cb(int level, const char* component, const char* message, void* user_data) {
|
||||
(void)user_data;
|
||||
fprintf(stderr, "[nostr][%s][%d] %s\n", component ? component : "unknown", level, message ? message : "");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (nostr_init() != NOSTR_SUCCESS) return 1;
|
||||
|
||||
nostr_set_log_callback(my_log_cb, NULL);
|
||||
nostr_set_log_level(NOSTR_LOG_LEVEL_TRACE);
|
||||
|
||||
/* ... your code ... */
|
||||
|
||||
nostr_cleanup();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration Notes
|
||||
|
||||
- Direct file logging to `debug.log` has been removed from websocket and NIP-13 internals.
|
||||
- To receive logs, register a callback with [`nostr_set_log_callback()`](nostr_core/nostr_log.h:24).
|
||||
- Use [`nostr_set_log_level()`](nostr_core/nostr_log.h:25) to reduce verbosity in production.
|
||||
|
||||
## 🌐 Supported Platforms
|
||||
|
||||
- **Linux** (x86_64, ARM64)
|
||||
|
||||
@@ -502,6 +502,7 @@ SOURCES="$SOURCES nostr_core/utils.c"
|
||||
SOURCES="$SOURCES nostr_core/nostr_common.c"
|
||||
SOURCES="$SOURCES nostr_core/core_relays.c"
|
||||
SOURCES="$SOURCES nostr_core/core_relay_pool.c"
|
||||
SOURCES="$SOURCES nostr_core/nostr_log.c"
|
||||
SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c"
|
||||
SOURCES="$SOURCES nostr_core/request_validator.c"
|
||||
|
||||
|
||||
+28
-23
@@ -6,6 +6,7 @@
|
||||
#include "nip001.h"
|
||||
#include "utils.h"
|
||||
#include "../cjson/cJSON.h"
|
||||
#include "nostr_log.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -175,12 +176,13 @@ int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
|
||||
if (attempts % timestamp_update_interval == 0) {
|
||||
current_timestamp = time(NULL);
|
||||
#ifdef ENABLE_DEBUG_LOGGING
|
||||
FILE* f = fopen("debug.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "PoW mining: %d attempts, best this round: %d, overall best: %d, goal: %d\n",
|
||||
attempts, best_difficulty_this_round, best_difficulty_overall, target_difficulty);
|
||||
fclose(f);
|
||||
}
|
||||
nostr_log_emitf(NOSTR_LOG_LEVEL_DEBUG,
|
||||
"nip013",
|
||||
"PoW mining: %d attempts, best this round: %d, overall best: %d, goal: %d",
|
||||
attempts,
|
||||
best_difficulty_this_round,
|
||||
best_difficulty_overall,
|
||||
target_difficulty);
|
||||
#endif
|
||||
// Reset best difficulty for the new round
|
||||
best_difficulty_this_round = 0;
|
||||
@@ -239,18 +241,22 @@ int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
|
||||
// Check if we've reached the target
|
||||
if (current_difficulty >= target_difficulty) {
|
||||
#ifdef ENABLE_DEBUG_LOGGING
|
||||
FILE* f = fopen("debug.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "PoW SUCCESS: Found difficulty %d (target %d) at nonce %llu after %d attempts\n",
|
||||
current_difficulty, target_difficulty, (unsigned long long)nonce, attempts + 1);
|
||||
|
||||
// Print the final event JSON
|
||||
char* event_json = cJSON_Print(test_event);
|
||||
if (event_json) {
|
||||
fprintf(f, "Final event: %s\n", event_json);
|
||||
free(event_json);
|
||||
}
|
||||
fclose(f);
|
||||
nostr_log_emitf(NOSTR_LOG_LEVEL_INFO,
|
||||
"nip013",
|
||||
"PoW SUCCESS: Found difficulty %d (target %d) at nonce %llu after %d attempts",
|
||||
current_difficulty,
|
||||
target_difficulty,
|
||||
(unsigned long long)nonce,
|
||||
attempts + 1);
|
||||
|
||||
// Print the final event JSON
|
||||
char* event_json = cJSON_Print(test_event);
|
||||
if (event_json) {
|
||||
nostr_log_emitf(NOSTR_LOG_LEVEL_DEBUG,
|
||||
"nip013",
|
||||
"Final event: %s",
|
||||
event_json);
|
||||
free(event_json);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -267,11 +273,10 @@ int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
|
||||
|
||||
#ifdef ENABLE_DEBUG_LOGGING
|
||||
// Debug logging - failure
|
||||
FILE* f = fopen("debug.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "PoW FAILED: Mining failed after %d attempts\n", max_attempts);
|
||||
fclose(f);
|
||||
}
|
||||
nostr_log_emitf(NOSTR_LOG_LEVEL_WARN,
|
||||
"nip013",
|
||||
"PoW FAILED: Mining failed after %d attempts",
|
||||
max_attempts);
|
||||
#endif
|
||||
|
||||
// If we reach here, we've exceeded max attempts
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
// Version information (auto-updated by increment_and_push.sh)
|
||||
#define VERSION "v0.4.12"
|
||||
#define VERSION "v0.4.13"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 4
|
||||
#define VERSION_PATCH 12
|
||||
#define VERSION_PATCH 13
|
||||
|
||||
/*
|
||||
* NOSTR Core Library - Complete API Reference
|
||||
@@ -193,6 +193,9 @@ extern "C" {
|
||||
// Authentication and request validation system
|
||||
#include "request_validator.h" // Request validation and authentication rules
|
||||
|
||||
// Logging callback API
|
||||
#include "nostr_log.h"
|
||||
|
||||
// Relay pool types and functions
|
||||
typedef enum {
|
||||
NOSTR_POOL_RELAY_DISCONNECTED = 0,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "nostr_log.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define NOSTR_LOG_BUFFER_SIZE 4096
|
||||
|
||||
static nostr_log_callback_t g_log_callback = NULL;
|
||||
static void* g_log_user_data = NULL;
|
||||
static int g_log_min_level = NOSTR_LOG_LEVEL_ERROR;
|
||||
|
||||
void nostr_set_log_callback(nostr_log_callback_t cb, void* user_data) {
|
||||
g_log_callback = cb;
|
||||
g_log_user_data = user_data;
|
||||
}
|
||||
|
||||
void nostr_set_log_level(nostr_log_level_t min_level) {
|
||||
if (min_level < NOSTR_LOG_LEVEL_ERROR) {
|
||||
g_log_min_level = NOSTR_LOG_LEVEL_ERROR;
|
||||
return;
|
||||
}
|
||||
if (min_level > NOSTR_LOG_LEVEL_TRACE) {
|
||||
g_log_min_level = NOSTR_LOG_LEVEL_TRACE;
|
||||
return;
|
||||
}
|
||||
g_log_min_level = (int)min_level;
|
||||
}
|
||||
|
||||
void nostr_log_vemitf(int level, const char* component, const char* format, va_list args) {
|
||||
if (!g_log_callback || !format) {
|
||||
return;
|
||||
}
|
||||
if (level < g_log_min_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
char message[NOSTR_LOG_BUFFER_SIZE];
|
||||
int written = vsnprintf(message, sizeof(message), format, args);
|
||||
if (written < 0) {
|
||||
return;
|
||||
}
|
||||
message[sizeof(message) - 1] = '\0';
|
||||
|
||||
g_log_callback(level,
|
||||
component ? component : "unknown",
|
||||
message,
|
||||
g_log_user_data);
|
||||
}
|
||||
|
||||
void nostr_log_emitf(int level, const char* component, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
nostr_log_vemitf(level, component, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef NOSTR_LOG_H
|
||||
#define NOSTR_LOG_H
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
NOSTR_LOG_LEVEL_ERROR = 1,
|
||||
NOSTR_LOG_LEVEL_WARN = 2,
|
||||
NOSTR_LOG_LEVEL_INFO = 3,
|
||||
NOSTR_LOG_LEVEL_DEBUG = 4,
|
||||
NOSTR_LOG_LEVEL_TRACE = 5
|
||||
} nostr_log_level_t;
|
||||
|
||||
typedef void (*nostr_log_callback_t)(
|
||||
int level,
|
||||
const char* component,
|
||||
const char* message,
|
||||
void* user_data
|
||||
);
|
||||
|
||||
void nostr_set_log_callback(nostr_log_callback_t cb, void* user_data);
|
||||
void nostr_set_log_level(nostr_log_level_t min_level);
|
||||
|
||||
void nostr_log_emitf(int level, const char* component, const char* format, ...);
|
||||
void nostr_log_vemitf(int level, const char* component, const char* format, va_list args);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NOSTR_LOG_H */
|
||||
@@ -1,5 +1,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "nostr_websocket_tls.h"
|
||||
#include "../nostr_core/nostr_log.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -24,14 +25,6 @@
|
||||
#define MAX_HEADER_SIZE 4096
|
||||
#define MAX_FRAME_SIZE 65536
|
||||
|
||||
// Debug logging (conditional compilation)
|
||||
#if defined(ENABLE_FILE_LOGGING) && defined(ENABLE_WEBSOCKET_LOGGING)
|
||||
static FILE* debug_log_file = NULL;
|
||||
static void debug_log_init(void);
|
||||
static void debug_log_message(const char* direction, const char* host, int port, const char* message);
|
||||
static const char* get_timestamp(void);
|
||||
#endif
|
||||
|
||||
// Transport layer abstraction
|
||||
typedef struct {
|
||||
int (*connect)(void* ctx, const char* host, int port);
|
||||
@@ -867,10 +860,15 @@ static int ws_send_frame(nostr_ws_client_t* client, ws_opcode_t opcode, const ch
|
||||
frame_len += payload_len;
|
||||
}
|
||||
|
||||
// Log outgoing message to debug.log
|
||||
#if defined(ENABLE_FILE_LOGGING) && defined(ENABLE_WEBSOCKET_LOGGING)
|
||||
// Emit outgoing message to consumer callback logger
|
||||
#if defined(ENABLE_WEBSOCKET_LOGGING)
|
||||
if (opcode == WS_OPCODE_TEXT && payload && payload_len > 0) {
|
||||
debug_log_message("SEND", client->host, client->port, payload);
|
||||
nostr_log_emitf(NOSTR_LOG_LEVEL_TRACE,
|
||||
"websocket",
|
||||
"SEND %s:%d: %s",
|
||||
client->host ? client->host : "",
|
||||
client->port,
|
||||
payload);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -960,14 +958,19 @@ static int ws_receive_frame(nostr_ws_client_t* client, ws_opcode_t* opcode, char
|
||||
ws_mask_payload(payload, len, mask);
|
||||
}
|
||||
|
||||
// Log incoming text messages to debug.log
|
||||
#if defined(ENABLE_FILE_LOGGING) && defined(ENABLE_WEBSOCKET_LOGGING)
|
||||
// Emit incoming text messages to consumer callback logger
|
||||
#if defined(ENABLE_WEBSOCKET_LOGGING)
|
||||
if (*opcode == WS_OPCODE_TEXT && len > 0) {
|
||||
// Null terminate for logging
|
||||
char temp_payload[len + 1];
|
||||
memcpy(temp_payload, payload, len);
|
||||
temp_payload[len] = '\0';
|
||||
debug_log_message("RECV", client->host, client->port, temp_payload);
|
||||
nostr_log_emitf(NOSTR_LOG_LEVEL_TRACE,
|
||||
"websocket",
|
||||
"RECV %s:%d: %s",
|
||||
client->host ? client->host : "",
|
||||
client->port,
|
||||
temp_payload);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -998,44 +1001,3 @@ static uint32_t ws_generate_mask(void) {
|
||||
return ((uint32_t)rand() << 16) | ((uint32_t)rand() & 0xFFFF);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Debug Logging Functions
|
||||
// ============================================================================
|
||||
|
||||
#if defined(ENABLE_FILE_LOGGING) && defined(ENABLE_WEBSOCKET_LOGGING)
|
||||
static void debug_log_init(void) {
|
||||
if (!debug_log_file) {
|
||||
debug_log_file = fopen("debug.log", "a");
|
||||
if (debug_log_file) {
|
||||
fprintf(debug_log_file, "\n=== NOSTR WebSocket Debug Log Started ===\n");
|
||||
fflush(debug_log_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const char* get_timestamp(void) {
|
||||
static char timestamp[32];
|
||||
struct timespec ts;
|
||||
struct tm *timeinfo;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
timeinfo = localtime(&ts.tv_sec);
|
||||
|
||||
// Format: HH:MM:SS.mmm (with milliseconds)
|
||||
strftime(timestamp, sizeof(timestamp), "%H:%M:%S", timeinfo);
|
||||
snprintf(timestamp + 8, sizeof(timestamp) - 8, ".%03ld", ts.tv_nsec / 1000000);
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
static void debug_log_message(const char* direction, const char* host, int port, const char* message) {
|
||||
debug_log_init();
|
||||
|
||||
if (debug_log_file) {
|
||||
fprintf(debug_log_file, "[%s] %s %s:%d: %s\n",
|
||||
get_timestamp(), direction, host, port, message);
|
||||
fflush(debug_log_file);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# Logging Delegation Plan for `nostr_core_lib`
|
||||
|
||||
## Objective
|
||||
|
||||
Refactor `nostr_core_lib` so it does not own log destinations such as `debug.log`, and instead emits logs through a consumer-provided callback. This allows host applications such as [`didactyl`](../README.md) to route all library logs into their own logging system with consistent formatting, level control, and file policy.
|
||||
|
||||
## Current State and Problem
|
||||
|
||||
The library currently writes directly to `debug.log` in multiple places:
|
||||
|
||||
- [`nostr_core_lib/nostr_websocket/nostr_websocket_openssl.c`](../nostr_websocket/nostr_websocket_openssl.c:1008)
|
||||
- [`nostr_core_lib/nostr_core/nip013.c`](../nostr_core/nip013.c:178)
|
||||
- [`nostr_core_lib/nostr_core/nip013.c`](../nostr_core/nip013.c:242)
|
||||
- [`nostr_core_lib/nostr_core/nip013.c`](../nostr_core/nip013.c:270)
|
||||
|
||||
This creates collisions with consumer logs when the host app also uses `debug.log` such as [`didactyl/src/debug.c`](../../src/debug.c:18).
|
||||
|
||||
## Target Design
|
||||
|
||||
Adopt a callback-based logging API inside `nostr_core_lib`:
|
||||
|
||||
1. Library owns no file handle for logs.
|
||||
2. Library emits structured log events by level and source.
|
||||
3. Consumer registers a callback once during startup.
|
||||
4. If no callback is set, logs are dropped by default.
|
||||
|
||||
### High-Level Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[nostr_core_lib internal code] --> B[nostr_log_emit level source message]
|
||||
B --> C{callback configured}
|
||||
C -->|Yes| D[consumer callback]
|
||||
C -->|No| E[drop log event]
|
||||
D --> F[didactyl debug_log routing]
|
||||
F --> G[didactyl log file or stdout]
|
||||
```
|
||||
|
||||
## API Additions
|
||||
|
||||
Add a small public logging API in [`nostr_core_lib/nostr_core/nostr_core.h`](../nostr_core/nostr_core.h):
|
||||
|
||||
- `nostr_log_level_t` enum with levels compatible with host systems
|
||||
- `nostr_log_callback_t` callback type
|
||||
- `void nostr_set_log_callback(nostr_log_callback_t cb, void* user_data);`
|
||||
- `void nostr_set_log_level(nostr_log_level_t min_level);`
|
||||
|
||||
Recommended callback shape:
|
||||
|
||||
```c
|
||||
typedef void (*nostr_log_callback_t)(
|
||||
int level,
|
||||
const char* component,
|
||||
const char* message,
|
||||
void* user_data
|
||||
);
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Keep ABI simple by formatting message inside the library before callback invocation.
|
||||
- Include `component` values such as `websocket` and `nip013`.
|
||||
|
||||
## Internal Implementation Plan
|
||||
|
||||
## Phase 1: Logging Core
|
||||
|
||||
1. Create `nostr_core_lib/nostr_core/nostr_log.h` and `nostr_core_lib/nostr_core/nostr_log.c`.
|
||||
2. Add internal singleton state:
|
||||
- callback pointer
|
||||
- callback user data
|
||||
- minimum log level
|
||||
3. Add internal helpers:
|
||||
- `nostr_log_emitf level component fmt ...`
|
||||
- stack buffer formatting with truncation safety
|
||||
4. Default behavior when no callback set: no-op.
|
||||
|
||||
## Phase 2: Replace File Logging in WebSocket Layer
|
||||
|
||||
1. Remove file-specific static globals from [`nostr_websocket_openssl.c`](../nostr_websocket/nostr_websocket_openssl.c:27).
|
||||
2. Remove `debug_log_init` and `debug_log_message` path at [`nostr_websocket_openssl.c`](../nostr_websocket/nostr_websocket_openssl.c:1005).
|
||||
3. Replace call sites:
|
||||
- outgoing frame logging near [`nostr_websocket_openssl.c`](../nostr_websocket/nostr_websocket_openssl.c:871)
|
||||
- incoming frame logging near [`nostr_websocket_openssl.c`](../nostr_websocket/nostr_websocket_openssl.c:964)
|
||||
4. Emit through `nostr_log_emitf` with component `websocket`.
|
||||
|
||||
## Phase 3: Replace File Logging in NIP-13
|
||||
|
||||
1. Replace `fopen`/`fprintf` blocks in [`nip013.c`](../nostr_core/nip013.c:178), [`nip013.c`](../nostr_core/nip013.c:242), and [`nip013.c`](../nostr_core/nip013.c:270).
|
||||
2. Emit equivalent debug lines via `nostr_log_emitf` with component `nip013`.
|
||||
3. Preserve existing compile-time verbosity guards if needed, but route output through callback.
|
||||
|
||||
## Phase 4: Wire Public API
|
||||
|
||||
1. Export new functions in [`nostr_core.h`](../nostr_core/nostr_core.h:1).
|
||||
2. Ensure object file inclusion in `nostr_core_lib` build scripts and static archive.
|
||||
3. Document behavior in [`nostr_core_lib/README.md`](../README.md).
|
||||
|
||||
## Phase 5: Consumer Integration in Didactyl
|
||||
|
||||
1. In didactyl initialization path near [`src/nostr_handler.c`](../../src/nostr_handler.c:1519), register `nostr_set_log_callback`.
|
||||
2. Adapter callback maps library levels to didactyl levels from [`src/debug.h`](../../src/debug.h:7).
|
||||
3. Prefix messages with source namespace such as `[nostr:websocket]` and `[nostr:nip013]`.
|
||||
|
||||
## Backward Compatibility Strategy
|
||||
|
||||
- Keep existing compile guards but change behavior from file output to callback emission.
|
||||
- If callback not set, behavior remains safe and silent.
|
||||
- No breaking changes for consumers not using logging.
|
||||
|
||||
## Testing Plan
|
||||
|
||||
### Unit Tests in `nostr_core_lib`
|
||||
|
||||
1. Add tests for callback registration and null behavior.
|
||||
2. Add tests for level filtering.
|
||||
3. Add tests that message formatting truncates safely and remains null terminated.
|
||||
|
||||
### Integration Checks in Didactyl
|
||||
|
||||
1. Start didactyl with debug enabled and callback wired.
|
||||
2. Verify websocket SEND and RECV lines appear through didactyl logger format from [`src/debug.c`](../../src/debug.c:31).
|
||||
3. Verify no separate websocket-only banner line from old path near [`nostr_websocket_openssl.c`](../nostr_websocket/nostr_websocket_openssl.c:1010).
|
||||
4. Verify LLM request log lines and nostr logs coexist in one log destination.
|
||||
|
||||
## Rollout Steps
|
||||
|
||||
1. Implement callback infrastructure and migrate websocket logging first.
|
||||
2. Validate no direct file writes remain in websocket module.
|
||||
3. Migrate nip013 logging.
|
||||
4. Add documentation and examples.
|
||||
5. Update didactyl wiring.
|
||||
6. Remove obsolete references to `ENABLE_FILE_LOGGING` for websocket logging behavior if no longer needed.
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
- Risk: excessive callback volume from websocket trace traffic.
|
||||
- Mitigation: implement minimum level filter in library and map websocket traffic to trace level.
|
||||
- Risk: callback reentrancy or thread concerns.
|
||||
- Mitigation: document callback must be fast and thread-safe, avoid heavy blocking in callback.
|
||||
- Risk: API drift across consumers.
|
||||
- Mitigation: keep callback signature minimal and stable.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- No direct `fopen("debug.log", ...)` remains in production logging paths in `nostr_core_lib`.
|
||||
- Consumer can set callback and receive websocket and nip013 logs.
|
||||
- Didactyl routes nostr_core_lib logs through its logger and single configured destination.
|
||||
- Documentation updated with migration and usage examples.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user