---
source_url: https://www.pubnub.com/docs/sdks/c/logging
title: Logging for C SDK
updated_at: 2026-09-24T12:25:41.000Z
sdk_name: PubNub C SDK
sdk_version: 1.0.0
---

# Logging for C SDK

PubNub C SDK, use the latest version: 1.0.0

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

This page explains how to configure logging in the PubNub C Software Development Kit (SDK): the log-level enum, runtime and compile-time filtering, the structured log-entry format, and how to author a custom logger provider. For the general logging philosophy shared across all PubNub SDKs, refer to [Logging practices](https://www.pubnub.com/docs/general/setup/logging.md).

## Logging architecture

Every context dispatches log entries through a small fan-out component that holds up to `PUBNUB_CFG_MAX_LOGGERS` logger providers (4 by default). At context creation, the SDK populates this list with:

1. The built-in default logger, unless the build selects `PUBNUB_PROVIDER_LOGGER=none`.
2. The `logger` field of `pubnub_config_t`, if you set it to a non-`NULL` provider.

In the common case, both slots are occupied: a compiled-in default logger and a configured `cfg.logger`. You can then register up to `PUBNUB_CFG_MAX_LOGGERS - 2` additional providers at runtime with `pubnub_logger_add()`. If the build sets `PUBNUB_PROVIDER_LOGGER=none` or you leave `cfg.logger` `NULL`, that slot is free and more providers fit before you hit the limit. Every registered provider receives every log entry that passes the runtime level check, in the order the providers were added. See [Multiple loggers and fan-out order](#multiple-loggers-and-fan-out-order).

Each log entry is a small stack-allocated struct. It carries a severity level, a type discriminator (text, structured object, error, network request, or network response), the source file and line that emitted it, and an identifier for the emitting context. There is no heap allocation anywhere in the logging path.

Two independent mechanisms control which entries actually produce output:

* **Compile-time stripping** removes call sites entirely from the binary for levels you don't compile in. See [Compile-time log level stripping](#compile-time-log-level-stripping).
* **Runtime filtering** drops entries below a per-context threshold that you can change while the program runs. See [Change the log level at runtime](#change-the-log-level-at-runtime).

## Log levels

`pubnub_log_level_t`, declared in `pubnub/providers/logger_types.h`, defines five active severities plus a sentinel that disables logging entirely:

```c
typedef enum pubnub_log_level {
    PUBNUB_LOG_LEVEL_TRACE   = 0x01,  /* Verbose trace events. */
    PUBNUB_LOG_LEVEL_DEBUG   = 0x02,  /* Debugging messages. */
    PUBNUB_LOG_LEVEL_INFO    = 0x04,  /* Informational messages. */
    PUBNUB_LOG_LEVEL_WARNING = 0x08,  /* Recoverable warnings. */
    PUBNUB_LOG_LEVEL_ERROR   = 0x10,  /* Error conditions. */
    PUBNUB_LOG_LEVEL_NONE    = 0x00   /* Disable all logging. */
} pubnub_log_level_t;
```

| Level | Value | Purpose |
| --- | --- | --- |
| `PUBNUB_LOG_LEVEL_TRACE` | `0x01` | Verbose trace events, including full network request and response bodies |
| `PUBNUB_LOG_LEVEL_DEBUG` | `0x02` | Debugging messages |
| `PUBNUB_LOG_LEVEL_INFO` | `0x04` | Informational messages, such as successful initialization |
| `PUBNUB_LOG_LEVEL_WARNING` | `0x08` | Recoverable warnings |
| `PUBNUB_LOG_LEVEL_ERROR` | `0x10` | Error conditions |
| `PUBNUB_LOG_LEVEL_NONE` | `0x00` | Disables all logging. Not a severity, only usable as a threshold or compiled mask |

The values are powers of two so they combine into a bitmask for compile-time stripping (`PUBNUB_CFG_LOG_LEVEL_COMPILED`), but they are also monotonically increasing (`TRACE < DEBUG < INFO < WARNING < ERROR`), which a compile-time static assertion in the header enforces. This dual role matters: the bitmask form lets a build strip an arbitrary combination of levels (for example, `INFO`, `WARNING`, and `ERROR` but not `TRACE` or `DEBUG`), while the ordering lets `pubnub_set_log_level()` and a provider's `set_level()` callback use simple threshold comparisons at runtime.

:::warning Network logs expose full request and response bodies at TRACE
At `PUBNUB_LOG_LEVEL_TRACE`, the SDK's own network-request and network-response log entries carry complete HTTP headers and bodies, including authentication tokens, signed-request query parameters, and message content. Never leave `TRACE` enabled in a production build, and treat any log output captured at `TRACE` as sensitive.
:::

## Change the log level at runtime

`pubnub_set_log_level()`, declared in `pubnub/client.h`, sets a per-context minimum severity threshold. Entries below this threshold are dropped by the fan-out component before any provider's `log()` callback runs.

* **Header**: `#include <pubnub/client.h>`
* **Types**: `pubnub_context_t`, `pubnub_log_level_t`, `pubnub_res_t`
* **Prerequisite**: an initialized context (`pubnub_create()` or `pubnub_init()`)
* **Blocking**: no, updates in-memory state only

```c
pubnub_res_t pubnub_set_log_level(pubnub_context_t* ctx, unsigned int level);
pubnub_log_level_t pubnub_logger_log_level(pubnub_context_t* ctx);
```

`pubnub_config_t` has a `log_level` field of type `pubnub_log_level_t`. When you zero-initialize the config struct (`= {0}`) it defaults to `PUBNUB_LOG_LEVEL_NONE` (silent). `pubnub_config_defaults()` sets it to `PUBNUB_LOG_LEVEL_INFO`. Set `cfg.log_level` before calling `pubnub_create()` or `pubnub_init()` to control the threshold before any SDK-internal log messages are emitted. After init, change the threshold with `pubnub_set_log_level()`. Query the current threshold with `pubnub_logger_log_level()`, which returns `PUBNUB_LOG_LEVEL_NONE` for a `NULL` or uninitialized context.

### Sample code

```c
#include <pubnub/pubnub.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.publish_key   = "demo";
    cfg.user_id        = "my-user-id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return -1;
    }

    /* Raise verbosity for troubleshooting. */
    pubnub_res_t rslt = pubnub_set_log_level(ctx, PUBNUB_LOG_LEVEL_DEBUG);
    if (PUBNUB_OK != rslt) {
        pubnub_destroy(ctx);
        return -1;
    }

    pubnub_destroy(ctx);
    return 0;
}
```

### Returns

`pubnub_set_log_level()` returns `PUBNUB_OK` on success. `pubnub_logger_log_level()` returns the context's current `pubnub_log_level_t` threshold.

## Compile-time log level stripping

`PUBNUB_CFG_LOG_LEVEL_COMPILED`, declared in `pubnub/providers/logger_types.h`, is a compile-time bitmask of the levels present in the binary at all. If a level's bit is absent from this mask, its call sites are eliminated entirely: no entry struct is constructed, no function pointer is invoked, and the associated strings and value trees are never referenced in the compiled output.

* **Header**: `#include <pubnub/providers/logger_types.h>` (pulled in automatically by `pubnub/log.h`)
* **Types**: the `PUBNUB_LOG_LEVEL_*_VALUE` integer constants, `PUBNUB_LOG_LEVEL_ALL` (`0x1F`)
* **Feature flag**: `-DPUBNUB_CFG_LOG_LEVEL_COMPILED=<mask>` at compile time, default `PUBNUB_LOG_LEVEL_ALL`

Two independent gates apply to every call to `pubnub_log_text()`, `pubnub_log_object()`, `pubnub_log_error()`, or the `PUBNUB_LOG_TEXT`/`PUBNUB_LOG_OBJECT`/`PUBNUB_LOG_ERR` macros, evaluated in this order:

1. **Compile-time gate.** The macros guard entry construction and dispatch behind `PUBNUB_LOG_LEVEL_ENABLED(lvl)`, a preprocessor-visible boolean expression. When you pass a literal `PUBNUB_LOG_LEVEL_*` constant, the normal case, the compiler constant-folds this check. If the level's bit is not in `PUBNUB_CFG_LOG_LEVEL_COMPILED`, the entire block is dead code and the compiler removes it: no entry struct, no call. This is what makes a stripped call free at runtime, not just cheap.
2. **Runtime gate.** Only reached once a call survives gate 1. The fan-out component compares the entry's level against the context's current threshold, set by `pubnub_set_log_level()`, and drops the entry if it falls below that threshold, or if no providers are registered. This gate is separate from compile-time stripping: a level that is compiled in but below the runtime threshold still pays for a runtime-conditional entry-struct construction and a comparison before being dropped.

Use the CMake convenience variable `PUBNUB_LOG_MIN_LEVEL` (`TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `NONE`) to set `PUBNUB_CFG_LOG_LEVEL_COMPILED` without computing the mask yourself. It translates to the mask covering that level and every more severe level. Passing `NONE` compiles out all logging call sites. See [Environment setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md) for how build profiles set these variables.

:::note Two similarly named macros, two different jobs
`PUBNUB_LOG_LEVEL_ENABLED(lvl)` (declared in `pubnub/providers/logger.h`) takes an enum value like `PUBNUB_LOG_LEVEL_DEBUG`. The `PUBNUB_LOG_TEXT`/`_OBJECT`/`_ERR` macros use it internally to guard entry construction. `PUBNUB_LOG_ENABLED(LEVEL)` (declared in `pubnub/providers/logger_types.h`) takes the bare level name, `DEBUG` rather than `PUBNUB_LOG_LEVEL_DEBUG`, and is the one usable directly in a preprocessor `#if` directive. Use `PUBNUB_LOG_LEVEL_ENABLED` in ordinary C code and `PUBNUB_LOG_ENABLED` when you need an `#if`.
`PUBNUB_LOG_LEVEL_ENABLED()` guards the macros' own entry construction, but if you build a `pubnub_log_value_t` tree yourself before calling `pubnub_log_object()`, that construction code is not automatically removed. On stack-constrained targets, wrap the construction in `#if PUBNUB_LOG_ENABLED(DEBUG)` (or the level you're using) so the tree-building code is also stripped.
:::

## Emit log entries

`pubnub/log.h` declares the functions you call to write a log entry through a context's configured loggers. Each is a no-op when `ctx` is `NULL` or when the level is compiled out.

* **Header**: `#include <pubnub/log.h>`
* **Types**: `pubnub_context_t`, `pubnub_log_level_t`, `pubnub_log_value_t`
* **Prerequisite**: an initialized context
* **Ownership / lifetime**: every string and value-tree argument is borrowed. The SDK does not retain any pointer after the call returns
* **Blocking**: no, each call dispatches synchronously to every registered provider on the calling thread

```c
void pubnub_log_text(pubnub_context_t*  ctx,
                      pubnub_log_level_t level,
                      const char*        message);

void pubnub_log_object(pubnub_context_t*         ctx,
                        pubnub_log_level_t        level,
                        const char*               label,
                        const pubnub_log_value_t* value);

void pubnub_log_error(pubnub_context_t*         ctx,
                       int                       error_code,
                       const char*               message,
                       const pubnub_log_value_t* details);
```

`pubnub_log_error()` always stamps the entry at `PUBNUB_LOG_LEVEL_ERROR`. There is no way to emit an error-type entry at any other severity. When `PUBNUB_CFG_MAX_LOG_MESSAGE_SIZE` is nonzero (default `512`), `pubnub/log.h` also declares `pubnub_log_text_formatted()`, a `printf`-style variant that formats into a stack buffer of that size and truncates silently if the formatted message doesn't fit.

### Sample code

```c
#include <pubnub/pubnub.h>

int main(void)
{
    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.publish_key   = "demo";
    cfg.user_id        = "my-user-id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return -1;
    }

    pubnub_log_text(ctx, PUBNUB_LOG_LEVEL_INFO, "Client initialized");

    /* An error entry always carries PUBNUB_LOG_LEVEL_ERROR. */
    pubnub_log_error(ctx, (int)PUBNUB_ERR_TIMEOUT, "Publish request timed out", NULL);

    pubnub_destroy(ctx);
    return 0;
}
```

### Returns

Both functions are `void`. There is no result to check. A `NULL` context or a compiled-out level silently produces no output.

## Structured log values

`pubnub_log_value_t`, declared in `pubnub/providers/logger_types.h`, is a stack-allocated tagged union for attaching structured data to a log entry via `pubnub_log_object()`. Arrays and maps are singly-linked lists of `pubnub_log_value_t` nodes rather than native containers, which keeps the whole tree heap-free:

```c
typedef struct pubnub_log_value {
    pubnub_log_value_type_t type;
    union {
        int bool_val;
        int64_t number_val;
        struct { const char* ptr; size_t len; } string_val;
        struct { struct pubnub_log_value* head; } array_val;
        struct { const char* key; struct pubnub_log_value* value; } map_val;
    } data;
    struct pubnub_log_value* next;
} pubnub_log_value_t;
```

Build a tree with the factory functions (`pubnub_log_value_string()`, `pubnub_log_value_number()`, `pubnub_log_value_bool()`, `pubnub_log_value_null()`, `pubnub_log_value_array_init()`, `pubnub_log_value_map_init()`) and the append/prepend helpers (`pubnub_log_value_array_append_node()`, `pubnub_log_value_map_set_entry()`), or use the convenience macros `PUBNUB_LOG_MAP_SET_STRING`/`_NUMBER`/`_BOOL` and `PUBNUB_LOG_ARRAY_APPEND_STRING`/`_NUMBER`/`_BOOL` to build one entry at a time. Walk a tree from a custom logger with the read-only accessors `pubnub_log_value_type()`, `pubnub_log_value_get_bool()`, `pubnub_log_value_get_number()`, `pubnub_log_value_get_string()`, `pubnub_log_value_first()`, `pubnub_log_value_key()`, and `pubnub_log_value_next()`. Every accessor is `NULL`-safe.

```c
#include <pubnub/providers/logger.h>

static void log_publish_params(pubnub_logger_provider_t* logger, const char* channel)
{
#if PUBNUB_LOG_ENABLED(DEBUG)
    pubnub_log_value_t map_ = PUBNUB_LOG_VALUE_NULL_INIT();
    map_.type = PUBNUB_LOG_VALUE_MAP;
    PUBNUB_LOG_MAP_SET_STRING(map_, channel, channel);
    PUBNUB_LOG_OBJECT(logger, PUBNUB_LOG_LEVEL_DEBUG, "params", &map_);
#endif
}
```

The tree must remain valid only for the duration of the `pubnub_log_object()` call. A provider's `log()` callback must not retain any pointer into it after returning. Because the values are borrowed, this pattern only works when the pointers you store in the tree (such as `channel` above) stay alive for at least that long, which stack-allocated locals and function parameters naturally satisfy.

## Default logger

Only a `stdout` backend ships in this SDK. Building with `PUBNUB_PROVIDER_LOGGER=stdout` (the default in the `full` and `minimal` build profiles) registers it automatically at context creation, alongside anything you set in `cfg.logger`. Building with `PUBNUB_PROVIDER_LOGGER=none` (the default in the `embedded` profile) registers no default logger at all. You must then supply `cfg.logger` or call `pubnub_logger_add()` to get any output. See [Environment setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md) for how build profiles set this variable.

When active, the stdout backend writes one line per entry in the form:

```text
2026-08-18T10:15:03.421Z PubNub-1a2b3c4d INFO   src/core/client.c:512 Client initialized
```

`ERROR`-level entries go to `stderr`. Every other level goes to `stdout`. Object entries print the label on its own line followed by an indented walk of the value tree (array elements as `-` bullets, map entries as `key: value`). Network request and response entries always print the method and URL. Headers and the full body print only when the active threshold is `PUBNUB_LOG_LEVEL_TRACE` and the response's content type looks text-like. Otherwise the body is truncated to 256 bytes.

## Custom loggers

Implement `pubnub_logger_provider_t`, declared in `pubnub/providers/logger.h`, to route log entries anywhere the built-in stdout backend can't reach, such as a file, a network sink, or an embedded target's own console driver:

```c
typedef struct pubnub_logger_provider {
    void (*log)(struct pubnub_logger_provider* self,
                const pubnub_log_entry_t*      entry);
    void (*set_level)(struct pubnub_logger_provider* self,
                      pubnub_log_level_t             min_level);
} pubnub_logger_provider_t;
```

* **Header**: `#include <pubnub/providers/logger.h>`
* **Types**: `pubnub_logger_provider_t`, `pubnub_log_entry_t` and its five subtypes (`pubnub_log_entry_text_t`, `_object_t`, `_error_t`, `_net_request_t`, `_net_response_t`)
* **Prerequisite**: none to implement the vtable, an initialized context to register it
* **Ownership / lifetime**: the provider is a **shared** object: the SDK never allocates, frees, or otherwise manages its lifecycle. You create it, keep it alive for as long as any context holds a reference to it, and release it yourself. Embed the vtable as the first member of your own struct so you can cast `self` back to your type inside the callbacks.
* **Thread / callback context**: callbacks run synchronously, inline, on whichever thread called the emitting function (or, for the SDK's own internal logging, whichever thread is driving request processing). They are never called from ISR context.
* **Buffers**: the `entry` passed to `log()`, and every string or value tree it points to, is valid only for the duration of the call. Copy anything you need before returning.

`log` is invoked for every entry that passes the runtime threshold. `set_level` is optional: leave it `NULL` if your provider doesn't need to react to threshold changes, since every call site null-checks it before invoking it. Neither the vtable nor its doc comments say whether `log()` may safely call back into the SDK (for example, publishing on the same context). Treat that as unsupported and avoid it.

### Sample code

The following implements a minimal custom logger that copies each entry's level and a short summary into an application-owned ring buffer, dispatching on `entry->type` the way the shipped stdout backend does:

```c
#include <pubnub/pubnub.h>
#include <pubnub/providers/logger.h>
#include <string.h>

#define RING_CAPACITY 16

typedef struct {
    pubnub_logger_provider_t base; /* Must be first member. */
    char                     summaries[RING_CAPACITY][64];
    pubnub_log_level_t       levels[RING_CAPACITY];
    size_t                   next;
} ring_logger_t;

static void ring_log(pubnub_logger_provider_t* self, const pubnub_log_entry_t* entry)
{
    ring_logger_t* ring = (ring_logger_t*)self;
    const char*    text = "";

    switch (entry->type) {
    case PUBNUB_LOG_ENTRY_TEXT:
        text = ((const pubnub_log_entry_text_t*)entry)->message;
        break;
    case PUBNUB_LOG_ENTRY_OBJECT:
        text = ((const pubnub_log_entry_object_t*)entry)->label;
        break;
    case PUBNUB_LOG_ENTRY_ERROR:
        text = ((const pubnub_log_entry_error_t*)entry)->error_message;
        break;
    case PUBNUB_LOG_ENTRY_NET_REQ:
        text = ((const pubnub_log_entry_net_request_t*)entry)->url;
        break;
    case PUBNUB_LOG_ENTRY_NET_RESP:
        text = ((const pubnub_log_entry_net_response_t*)entry)->url;
        break;
    default:
        break;
    }

    ring->levels[ring->next] = entry->level;
    strncpy(ring->summaries[ring->next], text ? text : "", sizeof(ring->summaries[0]) - 1);
    ring->summaries[ring->next][sizeof(ring->summaries[0]) - 1] = '\0';
    ring->next = (ring->next + 1) % RING_CAPACITY;
}

static void ring_set_level(pubnub_logger_provider_t* self, pubnub_log_level_t min_level)
{
    /* This provider doesn't filter on its own; the SDK's runtime
       threshold already does that before calling log(). */
    (void)self;
    (void)min_level;
}

int main(void)
{
    ring_logger_t ring = {0};
    ring.base.log       = ring_log;
    ring.base.set_level = ring_set_level;

    pubnub_config_t cfg = pubnub_config_defaults();
    cfg.subscribe_key = "demo";
    cfg.publish_key   = "demo";
    cfg.user_id        = "my-user-id";

    pubnub_context_t* ctx = pubnub_create(&cfg);
    if (NULL == ctx) {
        return -1;
    }

    pubnub_res_t rslt = pubnub_logger_add(ctx, &ring.base);
    if (PUBNUB_OK != rslt) {
        pubnub_destroy(ctx);
        return -1;
    }

    pubnub_log_text(ctx, PUBNUB_LOG_LEVEL_INFO, "Client initialized");

    pubnub_logger_remove(ctx, &ring.base);
    pubnub_destroy(ctx);
    return 0;
}
```

This is an illustrative implementation written for this page, not code copied from an SDK example. Build your own provider from the vtable contract above and adapt the dispatch pattern to your sink.

### Register and remove a logger

* **Header**: `#include <pubnub/log.h>`

```c
pubnub_res_t pubnub_logger_add(pubnub_context_t* ctx, pubnub_logger_provider_t* logger);
pubnub_res_t pubnub_logger_remove(pubnub_context_t* ctx, pubnub_logger_provider_t* logger);
void pubnub_logger_remove_all(pubnub_context_t* ctx);
```

`pubnub_logger_add()` returns `PUBNUB_OK` on success, `PUBNUB_ERR_INVALID_ARGUMENT` for a `NULL` or uninitialized `ctx`/`logger`, and `PUBNUB_ERR_QUEUE_FULL` once the context already holds `PUBNUB_CFG_MAX_LOGGERS` providers. The built-in default logger and `cfg.logger` each occupy a slot from the start, so with the default limit of `4`, only two further `pubnub_logger_add()` calls succeed. Raise `PUBNUB_CFG_MAX_LOGGERS` at compile time if you need more. `pubnub_logger_remove()` returns `PUBNUB_ERR_INVALID_ARGUMENT` if the provider isn't currently registered. `pubnub_logger_remove_all()` is `void` and safe to call on a `NULL` context.

The provider pointer you pass is borrowed: it must outlive the context, or you must remove it before destroying the context.

## Multiple loggers and fan-out order

When more than one logger is registered, the SDK dispatches every log entry that passes the runtime threshold to each provider's `log()` callback in turn, synchronously, in the order the providers were registered:

1. The built-in default logger (if the build didn't select `PUBNUB_PROVIDER_LOGGER=none`)
2. `cfg.logger`, if you set it
3. Any providers added later with `pubnub_logger_add()`, in call order

Removing a provider shifts the remaining providers to keep the list compact, but does not change their relative order. Every provider that implements `set_level` has its threshold synchronized to the context's current threshold when it is added, and receives any subsequent `pubnub_set_log_level()` change automatically. You don't need to call each provider's `set_level` yourself.

## Logging best practices

### Choose the compiled level range for your target

| Target | Recommended `PUBNUB_LOG_MIN_LEVEL` |
| --- | --- |
| Production embedded firmware | `NONE`, or `WARNING` if you need field diagnostics |
| Production server/desktop build | `WARNING` |
| Staging | `INFO` |
| Development | `DEBUG` |
| Deep troubleshooting with PubNub support | `TRACE` |

On stack- and flash-constrained embedded targets, prefer compile-time stripping over a high runtime threshold: a stripped level costs nothing at all, while a compiled-in level that's merely filtered at runtime still pays for entry construction on every call site.

### Protect sensitive data

Never compile in or enable `TRACE` in a build that handles production traffic. Network log entries at `TRACE` carry complete request and response headers and bodies, including auth tokens and signed-request query parameters, and this SDK has no equivalent of a content-length cap or key allowlist to redact them.

### Keep custom logger callbacks fast and non-reentrant

`log()` runs synchronously and inline on the caller's thread, so slow work inside it (disk I/O, network calls, blocking locks) delays whatever call emitted the entry. Copy what you need and return quickly. Do not call back into the SDK from inside `log()`.

### Size PUBNUB_CFG_MAX_LOGGERS for your use case

The default of `4` reserves two slots for the built-in default logger and `cfg.logger`, leaving room for two `pubnub_logger_add()` calls. Raise the value at compile time if you register more than two additional providers. `pubnub_logger_add()` fails with `PUBNUB_ERR_QUEUE_FULL` once the limit is reached.

For build-profile and CMake variable details, see [Environment setup](https://www.pubnub.com/docs/sdks/c/environment-setup.md). For the `logger` field's place among the rest of `pubnub_config_t`, see [Configuration](https://www.pubnub.com/docs/sdks/c/api-reference/configuration.md). For how the SDK reports operation outcomes independent of logging, see [Status Events](https://www.pubnub.com/docs/sdks/c/status-events.md).

## Terms in this document

* **PubNub** - PubNub is a real-time messaging platform that provides APIs and SDKs for building scalable applications. It handles the complex infrastructure of real-time communication, including: Message delivery and persistence, Presence detection, Access control, Push notifications, File sharing, Serverless processing with Functions and Events & Actions, Analytics and monitoring with BizOps Workspace, AI-powered insights with Illuminate.

Last updated at: 2026-09-24T12:25:41.000Z
