FluxaORM v2: Code-Generation-Based Go ORM for MySQL, Redis, ClickHouse and NATS JetStream
Guide
GitHub
Guide
GitHub
    • Introduction
    • Registry
    • Data Pools
    • Entities
    • Entity Fields
    • MySQL Indexes
    • Code Generation
    • Engine
    • Context
    • Schema Update
    • CRUD Operations
    • Transactions
    • Search
    • Redis Search
    • MySQL Queries
    • ClickHouse Queries
    • ClickHouse Schema
    • NATS and JetStream
    • Entity Change Events (CDC)
    • Consumers
    • Tasks and Job Runs
    • Transactional Outbox
    • Context Cache
    • Redis Cache
    • Fake Delete
    • Lifecycle Callbacks
    • Metrics
    • Redis Operations
    • Distributed Lock
    • Queries Log
    • Testing

Queries Log

Every MySQL query, Redis command, ClickHouse query and NATS operation executed through a fluxaorm.Context can be delivered to a log handler. Logging is configured per context: register a handler on the context you want to observe, and every context created from it with Clone() / CloneWithContext() inherits the same handlers. Contexts created directly from the engine are not affected.

import (
    "fmt"

    "github.com/latolukasz/fluxaorm/v2"
)

type MyLogger struct{}

func (l *MyLogger) Handle(ctx fluxaorm.Context, log map[string]any) {
    fmt.Printf("[%s/%s] %s %s\n", log["source"], log["pool"], log["operation"], log["query"])
}

ctx.RegisterQueryLogger(&MyLogger{}, fluxaorm.QueryLoggerOptions{MySQL: true, Redis: true, Clickhouse: true, Nats: true})

LogHandler

A handler implements the fluxaorm.LogHandler interface:

type LogHandler interface {
    Handle(ctx Context, log map[string]any)
}

Handle receives the context that executed the query, so you have access to ctx.GetMetaData(), the engine and the underlying context.Context. Handlers are called synchronously, right after the query finishes.

RegisterQueryLogger

RegisterQueryLogger(handler LogHandler, options QueryLoggerOptions)

type QueryLoggerOptions struct {
    MySQL      bool
    Redis      bool
    Clickhouse bool
    Nats       bool
}

Each true flag subscribes the handler to that source. Registering the same handler instance twice for the same source is a no-op, so calling RegisterQueryLogger again with more flags simply extends the subscription:

logger := &MyLogger{}
ctx.RegisterQueryLogger(logger, fluxaorm.QueryLoggerOptions{MySQL: true})        // MySQL only
ctx.RegisterQueryLogger(logger, fluxaorm.QueryLoggerOptions{Redis: true, Nats: true}) // now MySQL + Redis + NATS

Built-in Debug Logger

FluxaORM ships a coloured console logger that writes to stderr, useful during development and in tests:

// all four sources
ctx.EnableQueryDebug()

// selected sources only
ctx.EnableQueryDebugCustom(fluxaorm.QueryLoggerOptions{MySQL: true, Redis: true})

EnableQueryDebug() is equivalent to EnableQueryDebugCustom(QueryLoggerOptions{MySQL: true, Redis: true, Clickhouse: true, Nats: true}). Each entry is printed on its own line as:

FluxaORM <source logo> <pool> <operation> <duration in ms> <query>
<error, in red, only when present>

The source logo is MySQL, redis, CH or NATS; the pool column is padded to the longest registered pool code; the operation column is 14 characters wide; the time cell's background darkens as the query gets slower.

Log Entry Fields

The log map passed to Handle always contains source, pool, operation and query. The remaining keys are present only when applicable:

KeyTypePresentDescription
sourcestringalwaysmysql, redis, clickhouse or nats
poolstringalwaysData pool code. For NATS consumer fetches it is "<pool>/<consumer name>", for consumer handler errors it is the consumer name
operationstringalwaysOperation label, see the per-source tables below
querystringalwaysQuery / command text, see below
microsecondsint64timed entriesDuration in microseconds
startedint64timed entriesUnix time in nanoseconds when the query started
finishedint64timed entriesUnix time in nanoseconds when the query finished
errorstringon errorError message
missstringon cache missAlways the string "TRUE"
metafluxaorm.Metawhen metadata is setctx.GetMetaData() -- Meta is map[string]string with a Get(key string) string helper

All entries are timed except the two NATS consumer handler entries (CDC_HANDLE, TASK_HANDLE), which have no microseconds / started / finished.

MySQL (source="mysql")

operationquery
TRANSACTIONSTART TRANSACTION, COMMIT or ROLLBACK
EXECSQL text followed by the arguments formatted with %v, e.g. INSERT INTO ... VALUES(?,?) [1 Alice]
SELECTSQL text from DB.QueryRow / DB.Query (and all generated reads), arguments appended the same way

Newlines in SQL are replaced with spaces.

Redis (source="redis")

operationquery
lowercase Redis command name, e.g. get, set, hset, lrange, ft.search, flushdbfull command with arguments, e.g. get user:1
PIPELINE EXECevery command of the pipeline on its own line; a failing command has its error appended
LOCK OBTAINLOCK OBTAIN <key> TTL <ttl> WAIT <waitTimeout>; miss="TRUE" when the lock was not obtained
LOCK RELEASELOCK RELEASE <key>; miss="TRUE" when the lock was no longer held
LOCK TTLLOCK TTL <key>
LOCK REFRESHLOCK REFRESH <key> <ttl> where <ttl> is the TTL passed to Obtain, not the new one; miss="TRUE" when the lock was lost

miss="TRUE" is also set on get when the key is absent, on hget when the field is absent, on mget / hmget when any value is nil, and on ft.info when the index does not exist. GetSet logs its underlying get and (on a miss) set; IncrWithExpire logs a single incr. XReadGroup is never logged.

ClickHouse (source="clickhouse")

operationquery
EXECSQL text with arguments appended as %v
SELECTSQL text from QueryRow / Query

Newlines are replaced with spaces.

NATS (source="nats")

operationpoolquery
PUBLISHpool codesubject: <subject> (Publish, PublishWithAck)
PUBLISH_BATCHpool codemessages: <count>
PUBLISH_ASYNCpool codesubject: <subject>, logged when the ack (or error) arrives
FETCH<pool>/<consumer>resolve consumer (consumer lookup failed), 0 messages fetched (fetch error) or <n> messages fetched
CDC_HANDLEconsumer namesubject: <subject>; only on handler or ack error, always with error, not timed
TASK_HANDLEconsumer nameempty string; only when recording the job run result fails, always with error, not timed

See NATS, Consumers and Tasks.

Capturing Logs in Tests

fluxaorm.MockLogHandler is a ready-made handler that stores every entry:

logs := &fluxaorm.MockLogHandler{}
ctx.RegisterQueryLogger(logs, fluxaorm.QueryLoggerOptions{MySQL: true})

_, _, err := entities.UserEntityProvider.GetByID(ctx, 1)

selects := 0
for _, entry := range logs.Logs {
    if entry["operation"] == "SELECT" {
        selects++
    }
}
logs.Clear() // resets Logs to nil

See Testing for the full test tool set.

Edit this page
Last Updated: 9/21/26, 9:14 AM
Prev
Distributed Lock
Next
Testing