Skip to content

Debugging

Gerasimos (Makis) Maropoulos edited this page Oct 6, 2026 · 1 revision

When to use this page

  • A handler never fires, or fires with the wrong event name, and you cannot tell why just from reading your own code.
  • Ask or Connect seems to hang.
  • You want to see what neffos actually does when it builds your handlers, before shipping.

EnableDebug

func EnableDebug(printer Printer)
func Debugf(format string, args ...any)

type Printer interface {
	Printf(format string, args ...any)
}

type PrinterFunc func(format string, args ...any)

EnableDebug turns on debug logging for the whole process; there is no way to turn it back off at runtime. printer is anything with a Printf(string, ...any) method: a standard *log.Logger and a kataras/golog logger both qualify. Pass nil and neffos falls back to its own log.Logger prefixed "| neffos |". For a logger with another method name, or for log/slog, wrap a function in neffos.PrinterFunc:

import "github.com/kataras/neffos"

func main() {
	neffos.EnableDebug(nil) // or EnableDebug(log.Default()), or your own logger

	// slog has no Printf; adapt it.
	neffos.EnableDebug(neffos.PrinterFunc(func(format string, args ...any) {
		slog.Debug(fmt.Sprintf(format, args...))
	}))
	// ...
}

Before v0.1.0 EnableDebug took an any and looked for Debugf, Logf or Printf at runtime; a value with none of them silently fell back to the default logger. The typed parameter makes that a compile error instead.

What it logs, and what it costs

EnableDebug is meant to cover the build state of event handlers: the one-time reflection work NewStruct does when it turns your controller's methods into events. That happens once, when the Struct (or ConnHandler) is built, not per connection or per message, so turning on debug has no runtime cost for a plain Events/Namespaces handler. You will see lines like:

| neffos | Event ["Chat"] is handled by [main.lobby.OnChat] method
| neffos | Set namespace ["chat"] from method [main.lobby.Namespace]
| neffos | Field [main.lobby.Topic] marked as static on value [be kind, no spoilers]

One exception, worth knowing about if you use Scale out: the bundled redis and nats StackExchange implementations also call neffos.Debugf at runtime, for subscribe/unsubscribe failures, publish failures and reconnect events on the broker connection. If you enable debug on a server that uses either backend, you will see occasional log lines from live traffic too, not only from startup.

Reading the wire format

Most "why didn't this handler fire" questions are answered faster by knowing what actually went out, not by more logging. See Architecture for the full frame layout (<wait>;<namespace>;<room>;<event>;<isError>;<isNoOp>;<body>) and the ack handshake; a packet capture or a fmt.Printf("%q", payload) in a wrapped Socket (see Upgraders and Dialers, "Wrapping a socket") next to this reference usually settles it.

Symptoms table

Symptom Likely cause Where to look
A handler under OnNamespaceConnected never runs The other side never called Connect for that namespace; OnNamespaceConnected only fires after a successful connect handshake, it is not fired for free. Namespaces
msg.Err (or the returned error) is neffos.ErrBadNamespace You sent to, or tried to connect to, a namespace the other side never declared in its Events/Namespaces/WithTimeout. Check both sides' handler maps for a typo in the namespace string. Namespaces, Errors
Ask (or Connect, JoinRoom, ...) never returns A deadline-less context (context.TODO(), context.Background()) and a remote that never replies. It does return as soon as the connection closes, it just never does on its own without one of those. Always pass a context from context.WithTimeout. [[The ask method
A connection closes with neffos.ErrMessageTooBig on the receiver (CloseStatus is -1 there; the sender sees a real close frame, CloseStatus 1009) WithTimeout.MaxMessageSize (or Struct.SetMaxMessageSize) is set lower than a legitimate message. Raise it, or reject the oversized payload in your own handler before it gets that big. Timeouts, [[Close codes and shutdown
A connection closes on its own after a period of inactivity, with a timeout error, even though both sides are alive WithTimeout.PingInterval (or Struct.SetPingInterval) is shorter than the network's real round-trip time, so a pong cannot arrive before the next ping's deadline. Raise the interval. Timeouts

See also

  • Architecture, the wire format and the dispatch/reader goroutines this all runs on
  • Errors, the full error-classification helpers (IsTimeoutError, IsDisconnectError, IsCloseError)
  • Timeouts, ReadTimeout/WriteTimeout/PingInterval/MaxMessageSize in full

Clone this wiki locally