Skip to content

Commit 31d3079

Browse files
committed
Add LoggingConnection
1 parent a293d1f commit 31d3079

File tree

3 files changed

+249
-0
lines changed

3 files changed

+249
-0
lines changed

kernel/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ var (
1717
ErrInvalidBlockIndex = errors.New("invalid block index")
1818
ErrBlockProcessing = errors.New("failed to process block")
1919
ErrBlockRead = errors.New("failed to read block from disk")
20+
ErrInvalidCallback = errors.New("invalid callback function")
21+
ErrLoggingConnectionCreation = errors.New("failed to create logging connection")
2022
)

kernel/logging_connection.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package kernel
2+
3+
/*
4+
#include "kernel/bitcoinkernel.h"
5+
#include <stdlib.h>
6+
#include <stdint.h>
7+
8+
// Bridge function: exported Go function that C library can call
9+
// user_data contains the cgo.Handle ID as void* for callback identification
10+
extern void go_log_callback_bridge(void* user_data, char* message, size_t message_len);
11+
12+
// Wrapper function: C helper to create logging connection with Go callback
13+
// Converts Handle ID to void* and passes to C library
14+
static inline kernel_LoggingConnection* create_logging_connection_wrapper(uintptr_t context, kernel_LoggingOptions options) {
15+
return kernel_logging_connection_create((kernel_LogCallback)go_log_callback_bridge, (void*)context, options);
16+
}
17+
*/
18+
import "C"
19+
import (
20+
"runtime"
21+
"runtime/cgo"
22+
"sync"
23+
"unsafe"
24+
)
25+
26+
// LogLevel represents the logging level
27+
type LogLevel int
28+
29+
const (
30+
LogLevelTrace LogLevel = iota
31+
LogLevelDebug
32+
LogLevelInfo
33+
)
34+
35+
// LogCategory represents a logging category
36+
type LogCategory int
37+
38+
const (
39+
LogAll LogCategory = iota
40+
LogBench
41+
LogBlockStorage
42+
LogCoinDB
43+
LogLevelDB
44+
LogMempool
45+
LogPrune
46+
LogRand
47+
LogReindex
48+
LogValidation
49+
LogKernel
50+
)
51+
52+
// LogCallback is the Go callback function type for log messages.
53+
type LogCallback func(message string)
54+
55+
// LoggingOptions configures the format of log messages
56+
type LoggingOptions struct {
57+
LogTimestamps bool // Prepend a timestamp to log messages
58+
LogTimeMicros bool // Log timestamps in microsecond precision
59+
LogThreadNames bool // Prepend the name of the thread to log messages
60+
LogSourceLocations bool // Prepend the source location to log messages
61+
AlwaysPrintCategoryLevel bool // Prepend the log category and level to log messages
62+
}
63+
64+
// LoggingConnection wraps the C kernel_LoggingConnection.
65+
// Functions changing the logging settings are global and change
66+
// the settings for all existing kernel_LoggingConnection instances.
67+
type LoggingConnection struct {
68+
ptr *C.kernel_LoggingConnection
69+
handle cgo.Handle // Prevents callback GC until Delete() called
70+
}
71+
72+
//export go_log_callback_bridge
73+
func go_log_callback_bridge(user_data unsafe.Pointer, message *C.char, message_len C.size_t) {
74+
// Convert void* back to Handle - user_data contains Handle ID
75+
handle := cgo.Handle(user_data)
76+
// Retrieve original Go callback
77+
callback := handle.Value().(LogCallback)
78+
79+
goMessage := C.GoStringN(message, C.int(message_len))
80+
81+
// Call the actual Go callback
82+
callback(goMessage)
83+
}
84+
85+
func NewLoggingConnection(callback LogCallback, options LoggingOptions) (*LoggingConnection, error) {
86+
if callback == nil {
87+
return nil, ErrInvalidCallback
88+
}
89+
90+
// Create a handle for the callback - this prevents garbage collection
91+
// and provides a stable ID that can be passed through C code safely
92+
handle := cgo.NewHandle(callback)
93+
94+
cOptions := C.kernel_LoggingOptions{
95+
log_timestamps: C.bool(options.LogTimestamps),
96+
log_time_micros: C.bool(options.LogTimeMicros),
97+
log_threadnames: C.bool(options.LogThreadNames),
98+
log_sourcelocations: C.bool(options.LogSourceLocations),
99+
always_print_category_levels: C.bool(options.AlwaysPrintCategoryLevel),
100+
}
101+
102+
ptr := C.create_logging_connection_wrapper(C.uintptr_t(handle), cOptions)
103+
if ptr == nil {
104+
handle.Delete()
105+
return nil, ErrLoggingConnectionCreation
106+
}
107+
108+
connection := &LoggingConnection{
109+
ptr: ptr,
110+
handle: handle,
111+
}
112+
113+
runtime.SetFinalizer(connection, (*LoggingConnection).destroy)
114+
return connection, nil
115+
}
116+
117+
func (lc *LoggingConnection) destroy() {
118+
if lc.ptr != nil {
119+
C.kernel_logging_connection_destroy(lc.ptr)
120+
lc.ptr = nil
121+
}
122+
if lc.handle != 0 {
123+
// Delete exposes callback to garbage collection
124+
lc.handle.Delete()
125+
lc.handle = 0
126+
}
127+
}
128+
129+
func (lc *LoggingConnection) Destroy() {
130+
runtime.SetFinalizer(lc, nil)
131+
lc.destroy()
132+
}
133+
134+
// DisableLogging permanently disables the global internal logger.
135+
// This function should only be called once and is not thread-safe
136+
func DisableLogging() {
137+
C.kernel_disable_logging()
138+
}
139+
140+
// Global mutex for thread-safe category management
141+
var loggingMutex = sync.RWMutex{}
142+
143+
// AddLogLevelCategory sets the log level for a specific category or all categories
144+
func AddLogLevelCategory(category LogCategory, level LogLevel) {
145+
loggingMutex.Lock()
146+
defer loggingMutex.Unlock()
147+
C.kernel_add_log_level_category(C.kernel_LogCategory(category), C.kernel_LogLevel(level))
148+
}
149+
150+
// EnableLogCategory enables logging for a specific category or all categories
151+
func EnableLogCategory(category LogCategory) {
152+
loggingMutex.Lock()
153+
defer loggingMutex.Unlock()
154+
C.kernel_enable_log_category(C.kernel_LogCategory(category))
155+
}
156+
157+
// DisableLogCategory disables logging for a specific category or all categories
158+
func DisableLogCategory(category LogCategory) {
159+
loggingMutex.Lock()
160+
defer loggingMutex.Unlock()
161+
C.kernel_disable_log_category(C.kernel_LogCategory(category))
162+
}

kernel/logging_connection_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package kernel
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestLoggingOptions(t *testing.T) {
8+
// Test different logging options
9+
testCases := []struct {
10+
name string
11+
options LoggingOptions
12+
}{
13+
{
14+
name: "minimal options",
15+
options: LoggingOptions{
16+
LogTimestamps: false,
17+
LogTimeMicros: false,
18+
LogThreadNames: false,
19+
LogSourceLocations: false,
20+
AlwaysPrintCategoryLevel: false,
21+
},
22+
},
23+
{
24+
name: "full options",
25+
options: LoggingOptions{
26+
LogTimestamps: true,
27+
LogTimeMicros: true,
28+
LogThreadNames: true,
29+
LogSourceLocations: true,
30+
AlwaysPrintCategoryLevel: true,
31+
},
32+
},
33+
{
34+
name: "timestamps only",
35+
options: LoggingOptions{
36+
LogTimestamps: true,
37+
LogTimeMicros: false,
38+
LogThreadNames: false,
39+
LogSourceLocations: false,
40+
AlwaysPrintCategoryLevel: false,
41+
},
42+
},
43+
}
44+
45+
for _, tc := range testCases {
46+
t.Run(tc.name, func(t *testing.T) {
47+
conn, err := NewLoggingConnection(func(_ string) {}, tc.options)
48+
if err != nil {
49+
t.Fatalf("NewLoggingConnection() error = %v", err)
50+
}
51+
defer conn.Destroy()
52+
53+
if conn.ptr == nil {
54+
t.Error("Expected connection pointer to be set")
55+
}
56+
})
57+
}
58+
}
59+
60+
func TestAddLogLevelCategory(t *testing.T) {
61+
// Test adding log level categories
62+
AddLogLevelCategory(LogAll, LogLevelDebug)
63+
AddLogLevelCategory(LogAll, LogLevelInfo)
64+
AddLogLevelCategory(LogAll, LogLevelInfo) // Same operation twice should succeed
65+
66+
AddLogLevelCategory(LogBlockStorage, LogLevelDebug)
67+
AddLogLevelCategory(LogValidation, LogLevelTrace)
68+
AddLogLevelCategory(LogKernel, LogLevelInfo)
69+
}
70+
71+
func TestEnableDisableLogCategory(t *testing.T) {
72+
EnableLogCategory(LogBlockStorage)
73+
EnableLogCategory(LogBlockStorage) // Same operation twice should succeed
74+
DisableLogCategory(LogBlockStorage)
75+
DisableLogCategory(LogBlockStorage) // Same operation twice should succeed
76+
77+
EnableLogCategory(LogValidation)
78+
DisableLogCategory(LogValidation)
79+
80+
EnableLogCategory(LogKernel)
81+
DisableLogCategory(LogKernel)
82+
83+
EnableLogCategory(LogAll)
84+
DisableLogCategory(LogAll)
85+
}

0 commit comments

Comments
 (0)