go-proxy/log/impl.go
Virgil 30ff013158 docs(proxy): sharpen AX usage examples
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-05 02:34:53 +00:00

204 lines
5.1 KiB
Go

package log
import (
"os"
"strconv"
"strings"
"time"
"dappco.re/go/proxy"
)
// NewAccessLog creates an append-only access log.
//
// al := log.NewAccessLog("/var/log/proxy-access.log")
// defer al.Close()
func NewAccessLog(path string) *AccessLog {
return &AccessLog{path: path}
}
// Close releases the underlying file handle if the log has been opened.
//
// al := log.NewAccessLog("/var/log/proxy-access.log")
// defer al.Close()
func (l *AccessLog) Close() {
if l == nil {
return
}
l.mu.Lock()
defer l.mu.Unlock()
if l.file != nil {
_ = l.file.Close()
l.file = nil
}
}
// OnLogin writes a connect line such as:
//
// al.OnLogin(proxy.Event{Miner: &proxy.Miner{}})
// // 2026-04-04T12:00:00Z CONNECT 10.0.0.1 WALLET XMRig/6.21.0
func (l *AccessLog) OnLogin(e proxy.Event) {
if l == nil || e.Miner == nil {
return
}
l.writeConnectLine(e.Miner.IP(), e.Miner.User(), e.Miner.Agent())
}
// OnClose writes a close line such as:
//
// al.OnClose(proxy.Event{Miner: &proxy.Miner{}})
// // 2026-04-04T12:00:00Z CLOSE 10.0.0.1 WALLET rx=512 tx=4096
func (l *AccessLog) OnClose(e proxy.Event) {
if l == nil || e.Miner == nil {
return
}
l.writeCloseLine(e.Miner.IP(), e.Miner.User(), e.Miner.RX(), e.Miner.TX())
}
// NewShareLog creates an append-only share log.
//
// sl := log.NewShareLog("/var/log/proxy-shares.log")
// defer sl.Close()
func NewShareLog(path string) *ShareLog {
return &ShareLog{path: path}
}
// Close releases the underlying file handle if the log has been opened.
//
// sl := log.NewShareLog("/var/log/proxy-shares.log")
// defer sl.Close()
func (l *ShareLog) Close() {
if l == nil {
return
}
l.mu.Lock()
defer l.mu.Unlock()
if l.file != nil {
_ = l.file.Close()
l.file = nil
}
}
// OnAccept writes an accept line such as:
//
// sl.OnAccept(proxy.Event{Miner: &proxy.Miner{}, Diff: 100000, Latency: 82})
// // 2026-04-04T12:00:00Z ACCEPT WALLET diff=100000 latency=82ms
func (l *ShareLog) OnAccept(e proxy.Event) {
if l == nil || e.Miner == nil {
return
}
l.writeAcceptLine(e.Miner.User(), e.Diff, uint64(e.Latency))
}
// OnReject writes a reject line such as:
//
// sl.OnReject(proxy.Event{Miner: &proxy.Miner{}, Error: "Invalid nonce"})
// // 2026-04-04T12:00:00Z REJECT WALLET reason="Invalid nonce"
func (l *ShareLog) OnReject(e proxy.Event) {
if l == nil || e.Miner == nil {
return
}
l.writeRejectLine(e.Miner.User(), e.Error)
}
func (accessLog *AccessLog) writeConnectLine(ip, user, agent string) {
accessLog.mu.Lock()
defer accessLog.mu.Unlock()
if err := accessLog.ensureFile(); err != nil {
return
}
var builder strings.Builder
builder.WriteString(time.Now().UTC().Format(time.RFC3339))
builder.WriteByte(' ')
builder.WriteString("CONNECT")
builder.WriteString(" ")
builder.WriteString(ip)
builder.WriteString(" ")
builder.WriteString(user)
builder.WriteString(" ")
builder.WriteString(agent)
builder.WriteByte('\n')
_, _ = accessLog.file.WriteString(builder.String())
}
func (accessLog *AccessLog) writeCloseLine(ip, user string, rx, tx uint64) {
accessLog.mu.Lock()
defer accessLog.mu.Unlock()
if err := accessLog.ensureFile(); err != nil {
return
}
var builder strings.Builder
builder.WriteString(time.Now().UTC().Format(time.RFC3339))
builder.WriteByte(' ')
builder.WriteString("CLOSE")
builder.WriteString(" ")
builder.WriteString(ip)
builder.WriteString(" ")
builder.WriteString(user)
builder.WriteString(" rx=")
builder.WriteString(strconv.FormatUint(rx, 10))
builder.WriteString(" tx=")
builder.WriteString(strconv.FormatUint(tx, 10))
builder.WriteByte('\n')
_, _ = accessLog.file.WriteString(builder.String())
}
func (shareLog *ShareLog) writeAcceptLine(user string, diff uint64, latency uint64) {
shareLog.mu.Lock()
defer shareLog.mu.Unlock()
if err := shareLog.ensureFile(); err != nil {
return
}
var builder strings.Builder
builder.WriteString(time.Now().UTC().Format(time.RFC3339))
builder.WriteString(" ACCEPT")
builder.WriteString(" ")
builder.WriteString(user)
builder.WriteString(" diff=")
builder.WriteString(strconv.FormatUint(diff, 10))
builder.WriteString(" latency=")
builder.WriteString(strconv.FormatUint(latency, 10))
builder.WriteString("ms")
builder.WriteByte('\n')
_, _ = shareLog.file.WriteString(builder.String())
}
func (shareLog *ShareLog) writeRejectLine(user, reason string) {
shareLog.mu.Lock()
defer shareLog.mu.Unlock()
if err := shareLog.ensureFile(); err != nil {
return
}
var builder strings.Builder
builder.WriteString(time.Now().UTC().Format(time.RFC3339))
builder.WriteString(" REJECT ")
builder.WriteString(user)
builder.WriteString(" reason=\"")
builder.WriteString(reason)
builder.WriteString("\"\n")
_, _ = shareLog.file.WriteString(builder.String())
}
func (accessLog *AccessLog) ensureFile() error {
if accessLog.file != nil {
return nil
}
f, err := os.OpenFile(accessLog.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
accessLog.file = f
return nil
}
func (shareLog *ShareLog) ensureFile() error {
if shareLog.file != nil {
return nil
}
f, err := os.OpenFile(shareLog.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
shareLog.file = f
return nil
}