Oninit Logo
The Down System Specialists
+1-913-732-8892
+44-2081-337529
Partnerships Contact

Oninit® Log Ripper — Embedding the Library

The capture / output engine ships as a shared library (liboni_ripper.so) with a public C API so the same pipeline that powers the standalone oni_ripper daemon can be embedded in a long-running host application — a Java service via JNI, a Python pipeline via ctypes or cffi, another C / C++ daemon directly. The CLI binary is a thin wrapper around this library; the engine logic is identical.

When to embed vs. when to use the daemon

Use the standalone daemon when Embed the library when
The captured stream lands in files / JSON / CSV / Kafka and a downstream consumer reads from there. Captured changes flow into business logic in the same process — a queue, an event bus, an in-memory cache.
Operations team owns the lifecycle (systemd, monitoring, restart policy). Application owns the lifecycle and you don't want a second supervisor for the capture process.
You want the simplest possible deployment — one RPM/DEB install, edit YAML, start the service. You want stable-layout structs delivered via a callback instead of parsing a file/socket/Kafka payload.

The public header

The single public header is <oni_ripper.h>, installed at /usr/include/. It exposes opaque oni_config_t and oni_engine_t handles, a programmatic config builder mirroring every YAML key, the engine lifecycle, two callback shapes (per-transaction delivery, log line delivery), and a stats accessor.

Per-function signatures and descriptions live on the dedicated C API Reference page; this page covers the surrounding model — the callback shape, a worked example, build / link instructions, and signal handling.

Per-transaction callback (embed mode)

Pick the new embed output mode and register a callback. The engine never writes anywhere else — every committed transaction arrives in your callback as a stable-layout struct:

typedef struct {
    const char *op;          /* "INSERT" | "UPDATE" | "DELETE" */
    const char *owner;
    const char *table;
    uint64_t    lsn;
    const char *sql;         /* rendered SQL for the row */
} oni_record_t;

typedef struct {
    int64_t              txid;
    int                  worker_id;
    const char          *user;
    int                  userid;
    time_t               started;     /* 0 if NO_BEGIN */
    time_t               committed;   /* 0 if unknown */
    uint64_t             begin_lsn;
    uint64_t             commit_lsn;
    int                  has_begin;
    int                  has_commit;
    int                  nrecords;
    const oni_record_t  *records;
} oni_tx_t;

typedef int (*oni_tx_cb_t)(void *user, const oni_tx_t *tx);

The callback runs on the worker thread that captured the transaction. Returning 0 acks: the worker's last_committed_lsn advances and the next start resumes from the next transaction. Returning non-zero halts the engine without advancing the LSN, so the next start replays from the same source position — the right pattern when your downstream queue is backpressured or rejected the message.

Worked example

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <oni_ripper.h>

static oni_engine_t *g_engine;

static int on_tx(void *user, const oni_tx_t *tx) {
    int i;
    printf("tx %lld w=%d records=%d\n",
        (long long)tx->txid, tx->worker_id, tx->nrecords);
    for (i = 0; i < tx->nrecords; i++) {
        printf("  %s %s.%s lsn=%lx\n",
            tx->records[i].op,
            tx->records[i].owner,
            tx->records[i].table,
            (unsigned long)tx->records[i].lsn);
        /* Hand off to your queue / event bus / cache here. */
    }
    return 0;   /* ack -> LSN advances */
}

static void on_log(void *user, int sev, const char *tag, const char *msg) {
    fprintf(stderr, "[%s] %s\n", tag, msg);
}

static void on_signal(int sig) { oni_engine_stop(g_engine); }

int main(int argc, char **argv) {
    char err[512];
    oni_config_t *cfg = oni_config_new();

    oni_config_set_source(cfg, "stores_demo", "ol_informix1410", NULL, NULL);
    oni_config_add_table(cfg, "customer");
    oni_config_add_table(cfg, "order*");
    oni_config_set_logging(cfg, /*verbose*/ 1, 0,0,0,0,0, /*status*/ 60);
    oni_config_output_embed(cfg, on_tx, NULL);
    oni_config_set_log_callback(cfg, on_log, NULL);

    g_engine = oni_engine_new(cfg, err, sizeof(err));
    oni_config_free(cfg);
    if (!g_engine) { fprintf(stderr, "engine_new: %s\n", err); return 1; }

    signal(SIGINT,  on_signal);
    signal(SIGTERM, on_signal);

    oni_engine_start(g_engine);
    /* The library does NOT install signals - that's our job above.
     * Block until our handler triggers oni_engine_stop(). */
    while (1) {
        oni_stats_t s;
        if (oni_engine_stats(g_engine, &s) == 0 && s.active_workers == 0)
            break;
        sleep(1);
    }
    oni_engine_free(g_engine);
    return 0;
}

The same example ships as examples/embed_simple.c in the source tree.

Build & link

The recommended way is via pkg-config — the manifest carries the IBM CSDK link-line gotchas (the checkapi.o / libifgls / libthasf chain plus the $INFORMIXDIR/lib/esql rpath) so a one-liner builds:

cc embed_simple.c $(pkg-config --cflags --libs oni_ripper) \
    -o embed_simple

Without pkg-config, the link line is:

The CSDK must be installed and INFORMIXDIR set on the host running the embedder, same as for the standalone binary. See the Install page for the rest of the per-distro prerequisites.

Logging

By default the engine writes its tagged log lines ([STATUS], [LSN], [TX], [CRITICAL], [RELOAD], etc.) to stderr. Register a oni_log_cb_t via oni_config_set_log_callback() and every line routes into your application's logging framework instead. The callback receives severity (0=trace, 1=info, 2=warn, 3=critical), the bracketed tag, and the body of the line (no leading [TAG], no trailing newline) so the host can format it however its logger expects.

Signals

The library does not install signal handlers. The CLI wrapper installs its own (SIGTERM / SIGINT / SIGHUP / SIGCHLD) because that's a CLI concern. Embedders register whatever fits the host's process model and call oni_engine_stop() / oni_engine_reload() from those handlers. This is non- negotiable: a Java service under a JVM signal manager and a Python pipeline under uWSGI / Gunicorn don't want a second handler racing the host's.

Files installed

PathContents
/usr/lib64/liboni_ripper.so.0 Shared library (SONAME).
/usr/lib64/liboni_ripper.so Compatibility symlink for ld.
/usr/lib64/liboni_ripper.a Static library, same object set as the .so.
/usr/include/oni_ripper.h Public C API header. The only header that's part of the contract.
/usr/lib64/pkgconfig/oni_ripper.pc pkg-config manifest.

To discuss how Oninit ® can assist please call on +1-913-732-8892 or alternatively just send an email specifying your requirements.


You get all this for free.. think about what you get if you pay us