Logging and Diagnostics for Issue Reports

Tip

PJSUA-LIB readers — symbol equivalents are listed at the bottom of this page.

Goal

When something misbehaves in the library, two artifacts are usually needed to investigate:

  1. A log showing what happened, ideally at level 4 or higher (so internal flow is visible).

  2. For crashes / hangs, a stack trace of where the process was when it failed.

This guide covers how to capture both in two common scenarios — development (you’re actively reproducing) and production (the issue happened in the field, often on someone else’s device) — and how to package them for a useful bug report.

Two scenarios

Development / debugging

You can reproduce the issue at will, you have the source, you can run a debugger.

  • Build the library with debug symbols (-g for GCC/Clang; /Zi for MSVC; Debug configuration in Xcode/Android Studio).

  • Set log level to 5 or 6 for full trace.

  • Run under a debugger (gdb, lldb, Visual Studio / Xcode attached) so crashes drop you into a live backtrace.

Production / field

The issue happened on an end user’s deployment. You can’t attach a debugger; you have to persist diagnostic state and ship it back.

  • Build with debug symbols (yes, even for release) and keep the unstripped binaries (.so / .pdb / .dSYM) on your build server. Ship stripped binaries to users. Symbolicate later.

  • Configure the library to log to a file so messages survive process exit and can be uploaded.

  • Use at least level 4 for the file log (see Choosing a log level below).

  • Install a crash handler that captures backtraces and uploads them.

Capturing logs

Choosing a log level

The PJ_LOG macro takes a level 0–6 (pjlib/src/pj/log.c):

Level

Name

What you see

0

FATAL

fatal-only — nothing else

1

ERROR

unrecoverable errors

2

WARN

non-fatal anomalies

3

INFO

major events (start, REGISTER OK, call connected, …)

4

DEBUG

level-3 plus internal flow, SDP exchange details, callbacks fired

5

TRACE

level-4 plus per-packet, per-timer, per-callback

6

DETRC

exhaustive detail trace

Recommendations

  • Production: at least level 4. Level 4 carries enough context (state transitions, errors, key callbacks) to triage most issues. Going lower (3 or 2) is acceptable only when resources are genuinely tight (footprint, log volume) and nobody is on duty to investigate issues — at lower levels, most bug reports become unactionable.

  • Development: level 5 is the sweet spot for active debugging. Level 6 is useful for nasty races / timing bugs but produces a lot of noise.

  • Compile-time cap: PJ_LOG_MAX_LEVEL (default 5 in pjlib/include/pj/config.h) is the hard ceiling. Bumping it to 6 in config_site.h enables DETRC log macros; lowering it strips higher-level calls entirely at compile time.

Module-specific tracing

Beyond the runtime log level, some modules carry extra tracing that’s gated by a compile-time switch and needs a library rebuild to enable. It’s not normally on because the output is voluminous or expensive — but a deeper investigation of a specific module may need it.

Three patterns to be aware of:

You don’t need to enable these speculatively. If you know which module is involved (jitter buffer, DTLS handshake, video conference, etc.) and you’ve already filed an issue, mention what you know about the module — you may then be asked whether a specific trace is worth enabling.

Configuring the log via PJSUA2 / PJSUA-LIB

PJSUA2 (set on pj::EpConfig::logConfig):

EpConfig epcfg;
epcfg.logConfig.level        = 5;
epcfg.logConfig.consoleLevel = 4;
epcfg.logConfig.msgLogging   = true;          // include SIP message traffic
epcfg.logConfig.filename     = "/var/log/myapp/pjsip.log";
epcfg.logConfig.fileFlags    = PJ_O_APPEND;   // append on restart

endpoint.libCreate();
endpoint.libInit(epcfg);
endpoint.libStart();

PJSUA-LIB equivalent:

pjsua_logging_config log_cfg;
pjsua_logging_config_default(&log_cfg);
log_cfg.level         = 5;
log_cfg.console_level = 4;
log_cfg.msg_logging   = PJ_TRUE;
log_cfg.log_filename  = pj_str("/var/log/myapp/pjsip.log");
log_cfg.log_file_flags = PJ_O_APPEND;

pjsua_init(NULL, &log_cfg, NULL);

Defaults from pjsua_logging_config_default(): level=5, console_level=4, msg_logging=PJ_TRUE, decor as below.

Log decor

Decor is the bitmask that controls what each log line includes beyond the message itself — timestamp, source filename, thread markers, etc. The default produces lines like:

10:24:33.451   pjsua_core.c  Start handling IP address change

Each PJ_LOG_HAS_* flag toggles one column (pjlib/include/pj/log.h). Defaults (pjlib/src/pj/log.c) enable: PJ_LOG_HAS_TIME, PJ_LOG_HAS_MICRO_SEC, PJ_LOG_HAS_SENDER, PJ_LOG_HAS_NEWLINE, PJ_LOG_HAS_SPACE, PJ_LOG_HAS_THREAD_SWC (a marker on thread changes), PJ_LOG_HAS_INDENT, and on Windows PJ_LOG_HAS_COLOR (ANSI colour by level).

For threading visibility, the default decor already enables PJ_LOG_HAS_THREAD_SWC — a one-line marker emitted only when the logging thread changes, which is the lightweight default and usually enough. For a richer view, add PJ_LOG_HAS_THREAD_ID, which appends the thread name to every line (more readable in heavy-concurrency bugs at the cost of wider lines).

Other flags useful for issue reports that aren’t on by default:

  • PJ_LOG_HAS_LEVEL_TEXT — prefixes each line with ERROR: / WARN: / INFO: etc., useful when piping the log into grep or a viewer that doesn’t know the pjsip format.

  • PJ_LOG_HAS_DAY_OF_MON / PJ_LOG_HAS_YEAR / PJ_LOG_HAS_MONTH — date components. Useful for long-running deployments where a timestamp alone (which wraps at 24 hours) is ambiguous.

To customise, OR the flags you want with the existing decor:

epcfg.logConfig.decor |= PJ_LOG_HAS_THREAD_ID | PJ_LOG_HAS_LEVEL_TEXT;

Or take full control by setting decor to exactly what you want.

Thread-safety / dropped lines

Worth knowing for diagnostic work:

  • With the default PJ_LOG_USE_STACK_BUFFER=1 (pjlib/include/pj/config.h), each PJ_LOG call uses its own stack buffer — concurrent logs from different threads are safe and don’t corrupt each other. If you’ve set PJ_LOG_USE_STACK_BUFFER=0 for footprint, the writer becomes non-reentrant — concurrent calls from multiple threads can interleave or lose data.

  • PJLIB suppresses logging recursively on the same thread — if a log call’s internal formatting triggers another log (e.g. via a PJLIB API the formatter calls), the nested log is silently dropped. This also applies inside a custom LogWriter / cb: any PJ_LOG you emit from within the writer is dropped, not recursed. The mechanism prevents infinite recursion but can confuse “where did my log line go?” debugging.

pjsua CLI

$ ./pjsua --log-file=/tmp/pjsip.log --log-level=5 --app-log-level=4 --log-append
  • --log-file=path — write to file (otherwise stderr only).

  • --log-level=N — file/callback level (default 5).

  • --app-log-level=N — console level (default 4).

  • --log-append — append on each run instead of overwriting.

Custom log writer

Sometimes you need the library’s log to land somewhere other than stdout or a file — for example a platform logger (logcat, os_log, OutputDebugString, syslog), an in-app log viewer, or your own crash/diagnostic uploader. Install a custom writer and the library delegates every log line to it.

PJSUA2 — subclass pj::LogWriter and override write():

class MyLogWriter : public LogWriter {
public:
    void write(const LogEntry &entry) override {
        // entry.msg, entry.level, entry.threadId, entry.time, ...
        // Forward to your sink:
        //   - syslog():            POSIX system log
        //   - os_log():            Apple unified logging
        //   - __android_log_print: Android logcat
        //   - OutputDebugStringA:  Windows debug stream
        //   - your own uploader
    }
};

MyLogWriter writer;        // must outlive the Endpoint
epcfg.logConfig.writer = &writer;

PJSUA-LIB — set the C callback on pjsua_logging_config::cb:

static void my_log_callback(int level, const char *data, int len) {
    /* level: pjlib log level 0–6
     * data:  full formatted line, may include trailing newline
     * len:   length of `data`
     * Forward to your sink.
     */
}

pjsua_logging_config_default(&log_cfg);
log_cfg.cb = &my_log_callback;
pjsua_init(NULL, &log_cfg, NULL);

Raw PJLIB — pj_log_set_log_func() (signature is the same pj_log_func):

pj_log_set_log_func(&my_log_callback);

Java (PJSUA2 via SWIG) — subclass LogWriter and override write. The Android sample does this to push into logcat via System.out (see pjsip-apps/src/swig/java/android/):

class MyLogWriter extends LogWriter {
    @Override
    public void write(LogEntry entry) {
        System.out.println(entry.getMsg());
        // Or, for a custom logcat tag:
        // android.util.Log.d("pjsip", entry.getMsg());
    }
}

MyLogWriter lw = new MyLogWriter();   // keep this reference alive!
epCfg.getLogConfig().setWriter(lw);
endpoint.libInit(epCfg);

Python (PJSUA2 via SWIG) — subclass pj.LogWriter (the bundled pjsip-apps/src/swig/python/test.py has a working example):

import pjsua2 as pj
import sys

class MyLogWriter(pj.LogWriter):
    def write(self, entry):
        sys.stdout.write("pjsip: " + entry.msg + "\n")

ep_cfg = pj.EpConfig()
lw = MyLogWriter()              # keep this reference alive!
ep_cfg.logConfig.writer = lw
ep_cfg.logConfig.decor &= ~(pj.PJ_LOG_HAS_CR | pj.PJ_LOG_HAS_NEWLINE)

ep = pj.Endpoint()
ep.libCreate()
ep.libInit(ep_cfg)

Warning

The callback may be invoked from any thread — including pjsip worker threads and timer callbacks. Keep the writer thread-safe and non-blocking. Avoid calling pjsip APIs from within it (re-entrancy risk). Don’t allocate or take locks that other pjsip code might hold.

Java / Python: keep the writer object referenced for the lifetime of the Endpoint. If the only reference is the SWIG wrapper’s, the garbage collector may free the writer while the library still holds a pointer to it — leading to a use-after-free crash. Store it as an instance / module variable.

For applications that link pjnath / pjlib without PJSUA-LIB, the same callback is set via the raw PJLIB API pj_log_set_log_func():

static void my_log_writer(int level, const char *data, int len) {
    /* write to file, syslog, etc. */
}

pj_log_set_level(5);
pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME |
                 PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE);
pj_log_set_log_func(&my_log_writer);

Per-platform fetching

Where the default log ends up, and how to capture it, varies by platform. The default writer (pjlib/src/pj/log_writer_stdout.c) is just printf() — so behaviour depends on where stdout goes on each platform.

Linux / macOS / *nix

Default goes to stdout. Capture by redirecting or using the --log-file flag (or pjsua_logging_config::log_filename):

./myapp 2>&1 | tee pjsip.log
./myapp >pjsip.log 2>&1

Or set log_filename directly so the library writes the file itself (preferable for daemons).

Windows

Default writer uses printf() with ANSI colour codes. On modern Windows the console renders them natively. For redirection:

myapp.exe > pjsip.log 2>&1

For GUI / service apps without a console, the simplest path is to use log_filename so the library writes a file directly. If you need OutputDebugString (visible in DebugView / WinDbg), install a custom writer and call OutputDebugStringA from it.

Android

The library does not ship a logcat-aware writer. What the default printf() does depends on which side of the JNI boundary the message originates:

  • PJSUA2 from Java / Kotlin (the typical app pattern): override pj::LogWriter and call System.out.println(entry.getMsg()). Android’s runtime forwards System.out to logcat under the System.out tag, so logs show up in logcat automatically. This is what the bundled pjsua2 Java / Kotlin Android samples do (see pjsip-apps/src/swig/java/android/). For a custom tag, call android.util.Log.d("pjsip", entry.getMsg()) instead of println.

  • Native NDK code linking the library directly: printf() / stdout from the native side is not forwarded to logcat. Set log_filename to write to a file, or install a custom writer in C that calls __android_log_print(ANDROID_LOG_INFO, "pjsip", "%.*s", len, data);.

Recommended file location: the app’s internal files directory, context.getFilesDir(). Always available, app- private, requires no permission. Path looks like /data/data/<package>/files/pjsip.log.

// Java / Kotlin — pass to pjsip as the log_filename
File logFile = new File(context.getFilesDir(), "pjsip.log");

The trade-off is that internal storage isn’t directly accessible to the user. Pair this with a “Share log” UI affordance in your app that surfaces the file via a Share Intent (ACTION_SEND with a FileProvider URI) — the user picks email / drive / chat to send it. This is the production pattern.

getExternalFilesDir(null) (/sdcard/Android/data/<pkg>/files/) is the alternative when you want the file visible to the user via a file manager without an in-app affordance — but it can return null when external storage isn’t mounted, so it’s not always available. Don’t rely on it as the only location in production.

For development builds, adb pull works against internal storage via run-as (debuggable build only):

# External files dir (any build):
adb pull /sdcard/Android/data/com.example.myapp/files/pjsip.log

# Internal files dir (debuggable build only):
adb exec-out run-as com.example.myapp cat files/pjsip.log > pjsip.log

Forwarding to logcat (custom writer):

__android_log_print(ANDROID_LOG_INFO, "pjsip", "%.*s", len, data);

Watch live: adb logcat -s pjsip.

iOS / macOS apps

Same situation as Android — the default printf() goes to stdout, which on iOS isn’t user-accessible.

Recommended file location: the app’s Documents directory (NSDocumentDirectory), which becomes user-visible if the app’s Info.plist sets:

<key>UIFileSharingEnabled</key>           <true/>
<key>LSSupportsOpeningDocumentsInPlace</key>  <true/>

With both keys set, the log file shows up under the app in the iOS Files app, so the user can email / AirDrop / upload it to your support endpoint without needing a developer’s help. From a Mac with a connected device, the file is also accessible via Finder → Files when the device is connected.

For background apps without a UI, install a custom writer that calls os_log / os_log_with_type — messages then appear in Console.app and the device’s unified logging stream. os_log is preferred over NSLog for non-debug logging.

Capturing media (for audio / video issues)

For issues that show up as bad audio (silence, distortion, echo, dropouts, one-way audio) or bad video (frozen, flipped, garbled, lag), the actual audio / video symptom is often more useful for investigation than the log alone. The simplest way to capture it is to record what flows through the conference bridge: attach a recorder port on the same slot the call is connected to, and the library writes a .wav (audio) or .avi (video) you can attach to the issue.

PJSUA2:

// Audio — record the call leg as it sounds locally.
AudioMediaRecorder rec;
rec.createRecorder("/path/to/writable/call.wav");
// On Android, pass the path from context.getFilesDir() (or
// context.getExternalFilesDir(null)) through JNI; on iOS, the
// Documents directory; on desktop, any writable path.

// Hook to the call's audio leg (after the call connects).
AudioMedia callAudio = call.getAudioMedia(/*med_idx*/ -1);
callAudio.startTransmit(rec);   // record incoming audio

// To also capture what you sent, transmit the local mic into rec too:
AudDevManager &mgr = ep.audDevManager();
mgr.getCaptureDevMedia().startTransmit(rec);

// ... call runs ...

// Stop & close — the WAV header is finalised on destruction.
// `rec` going out of scope is enough; explicit cleanup not required.

Video (PJSUA2) — pj::VideoRecorder works analogously against the video conference bridge; see the AVI writer reference in pjmedia/include/pjmedia/avi_stream.h.

PJSUA-LIB equivalents: pjsua_recorder_create() for audio (and the underlying pjmedia_wav_writer_port_create() if you need raw PJMEDIA control). Then pjsua_conf_connect() to wire the recorder’s slot to the call leg’s slot.

Where to put the file: same recommendation as for log files — on Android use getExternalFilesDir(null), on iOS use the Documents directory with file-sharing enabled, on desktop a writable path the user knows.

For diagnostic needs beyond a plain WAV recording — inspecting raw PCM frames, feeding them into a custom format or an ML pipeline, or otherwise tapping the audio path programmatically — see Audio Frame Manipulation, which covers pj::AudioMediaPort and the PJSUA-LIB / PJMEDIA-level alternatives.

Note

Recording captures media as it appears inside the library’s conference bridge — i.e. after RTP receive and decoding, before playback. That’s the right perspective for most issues (“the library thinks the audio is X”). When the problem is in how the OS audio device renders sound, or how it captured it in the first place, the bridge recording won’t see it — see External observers below.

External observers

Some classes of issue are only visible from outside the library:

  • On-wire SIP / RTP problems (packets dropped by a NAT, malformed packets from a peer, codec mismatches that survive SDP negotiation but break decoding): capture with Wireshark / tshark / tcpdump on the same network the call is on. Wireshark’s Telephony → VoIP Calls view stitches SIP + RTP together and can play back the captured RTP stream as audio.

  • OS audio device behaviour (mic muted by the OS, AGC over- aggressive, sample-rate conversion artefacts): capture the device-level audio with the platform’s tools — Audio MIDI Setup on macOS, adb shell dumpsys audio on Android, Windows Sound control panel, etc.

  • Echo from acoustic coupling: indistinguishable from software echo in logs; a separate microphone recording of the room is what’s needed.

Mention any external captures in the bug report — they’re useful to know about even if you don’t attach them upfront.

Capturing stack traces

Build with debug symbols

In all cases, the library must be built with debug info, or backtraces will be unsymbolised addresses you can’t act on.

  • GCC / Clang (autotools, CMake, Makefile builds): add -g to CFLAGS / CXXFLAGS. For production, you typically want -O2 -g (full optimisation, full symbols), then strip symbols out of the shipping binary while keeping an unstripped copy on the build server.

    ./configure CFLAGS="-O2 -g" && make
    # On the build server, keep libpjproject.so.unstripped
    cp libpjproject.so libpjproject.so.unstripped
    strip libpjproject.so
    
  • MSVC: build with /Zi and link with /DEBUG to produce a .pdb file alongside the .exe / .dll. Ship the binary, keep the .pdb on the symbol server.

  • Xcode (iOS / macOS): Debug configuration produces full symbols. For Release, set Generate Debug Symbols = Yes and Strip Debug Symbols During Copy = No — the build emits a .dSYM bundle alongside the app. Archive the .dSYM for post-hoc symbolication.

  • Android NDK: APP_OPTIM=release doesn’t strip by default — symbols are in the unstripped .so under obj/local/<abi>/. Gradle’s release build strips automatically; keep the unstripped .so for ndk-stack.

Development: under a debugger

The simplest path. Run your app under the debugger from the start; on crash, dump the backtrace.

Linux:

gdb --args ./myapp
(gdb) run
... (crash) ...
(gdb) thread apply all bt    # backtraces of every thread

macOS:

lldb -- ./myapp
(lldb) run
... (crash) ...
(lldb) thread backtrace all

Windows: F5 in Visual Studio; on crash, Debug → Windows → Call Stack, then Threads for other threads’ stacks.

iOS / macOS app: launch from Xcode; crash drops into the debugger with the backtrace visible in the navigator.

Android: launch from Android Studio with the native debugger enabled, or attach ndk-gdb / lldb to a running process.

Production: crashes after the fact

You can’t be in the debugger when the user hits the crash. Two strategies:

Coredumps + post-mortem (Linux / macOS servers):

ulimit -c unlimited
# ... crash produces a 'core' file ...
gdb /path/to/myapp /path/to/core
(gdb) thread apply all bt

For systemd services, configure LimitCORE and CoreDumpFilter so cores actually get written.

Crash reporter integration:

  • Sentry / Crashlytics / Bugsnag: link their SDK and your app uploads crashes (with backtraces) automatically. They handle symbolication if you upload your .dSYM / unstripped .so / .pdb.

  • Custom: install a signal handler (SIGSEGV, SIGABRT) that captures raw frame addresses via backtrace() (glibc / macOS — async-signal-safe), writes them with write(2) to a pre-opened file descriptor, and re-raises. Resolve the addresses to symbols offline with addr2line against the unstripped binary. Do not call backtrace_symbols(), malloc, printf, or PJ_LOG from the handler — they aren’t async-signal-safe and can deadlock or corrupt state. For anything beyond this minimal pattern, prefer a dedicated crash-reporter library.

Android tombstones: native crashes produce tombstones in /data/tombstones/ (system-owned, not app-readable). The portable way to retrieve them is via the bug report:

adb bugreport bugreport.zip
# tombstone files are inside, under FS/data/tombstones/

Direct access via adb shell ls /data/tombstones works only when the shell user has access (typically rooted devices / userdebug builds); on stock release builds root is required.

Symbolicate with ndk-stack:

ndk-stack -sym path/to/obj/local/arm64-v8a -dump tombstone.txt

iOS crash logs: Xcode → Window → Devices and Simulators → View Device Logs. .ips files can be symbolicated via symbolicatecrash (in Xcode’s tools) using the archived .dSYM.

Windows minidumps: a custom SetUnhandledExceptionFilter + MiniDumpWriteDump writes a .dmp file. Analyze with WinDbg:

windbg -z crash.dmp
> !analyze -v
> ~*kn               # backtraces of every thread

Hangs (live backtrace, no crash)

When the process is stuck rather than crashed — deadlock, spinning, blocked on a syscall — capture a live backtrace of every thread:

  • Linux: gstack <pid> (one-shot, no debugger setup), or gdb -p <pid> then thread apply all bt.

  • macOS: sample <pid> 5 samples for 5 seconds and prints a full per-thread trace.

  • Windows: attach Visual Studio’s debugger and break, then look at all threads’ call stacks.

  • Android: adb shell debuggerd -b <pid> writes a tombstone for the running process without crashing it.

A live backtrace from a hung process is often more useful than a log, because it shows exactly where each thread is parked.

Reporting checklist

For an issue report that can be acted on, attach:

  • PJSIP version: usually already in the log — pjsua’s initialisation prints a line like pjsua version 2.17 for Linux-x86_64 initialized at level 3, which is enough. For git builds, also include the commit SHA so the exact source tree can be identified.

  • Platform: OS / OS version, CPU architecture, SSL backend (OpenSSL, GnuTLS, Mbed TLS, Schannel), SRTP source (bundled vs external).

  • Build flags: ./configure line and any config_site.h overrides.

  • A log at level 5 whenever possible (level 4 is acceptable fallback when level 5 isn’t feasible — e.g. production footprint), covering the relevant time window — full session preferred, not just the failing moment.

  • For crashes: backtrace from the debugger / tombstone / .dSYM-symbolicated .ips / minidump analysis. Including all threads matters when the crash involves locking.

  • For hangs: live backtrace of every thread (see Hangs above).

  • For audio / video issues: a media recording captured at the conference bridge — see Capturing media (for audio / video issues) for the recipe.

  • For network / RTP / NAT-flavoured issues: a packet capture taken concurrently with the log, on the network interface the call ran over. Wireshark’s Telephony → VoIP Calls view stitches SIP + RTP together and lets you confirm whether packets were actually delivered, what SDP was on the wire, whether RTP was flowing in both directions, and so on. tcpdump -i any -w call.pcap from a server, or Wireshark capture filtered to the SIP port plus the negotiated RTP range on a desktop, is usually enough.

    A common diagnostic blindspot: an ALG (Application Layer Gateway) or other SIP-aware middlebox (some routers, firewalls, carrier-grade NAT, SBCs) can rewrite SIP messages in flight — Contact header, SDP c= line, RTP ports — without your or the peer’s knowledge. The log shows what pjsip sent, but the capture shows what’s actually on the wire.

  • Reproduction steps: what you did to trigger it.

  • Custom code paths that change how the library runs. The bug itself may or may not be in the custom code, but custom extensions shape the runtime environment — message flow, media flow, thread scheduling — and the actual runtime needs to be known to reason about the issue. Call out anything that isn’t stock PJSIP:

    • Patches on top of a release — if it’s “PJSIP X.Y plus a few PRs”, list the PR numbers; for local changes not yet upstream, attach the diff.

    • Custom PJSIP modules (pjsip_module) — hook the SIP message pipeline.

    • Custom PJMEDIA ports (pjmedia_port) — inserted into the conference bridge or stream. See Customizing the Audio Stream Port and Custom port lifecycle for the contract.

    • Custom transport adapter (pjmedia_transport_adapter_create) — wraps the underlying media transport; see Transport Adapter.

    • Third-party media — custom codecs, custom audio / video devices; see Integrating Third Party Media Stack into PJSUA-LIB.

    • Custom PJSUA-LIB callback handlers doing significant work (synchronous I/O, blocking calls) on a pjsip thread.

    If none of these apply, say so explicitly (“stock PJSIP X.Y.Z, no patches, no custom modules / ports”) — it saves a round-trip asking.

Note

Check if there is sensitive info in the log before sharing. Most of what pjsip logs is not — SIP signalling, SDP negotiation, RTP stats — so don’t over-redact.

PJSUA-LIB equivalents

PJSUA2

PJSUA-LIB

pj::LogConfig::level

pjsua_logging_config::level

pj::LogConfig::consoleLevel

pjsua_logging_config::console_level

pj::LogConfig::msgLogging

pjsua_logging_config::msg_logging

pj::LogConfig::filename

pjsua_logging_config::log_filename

pj::LogConfig::fileFlags

pjsua_logging_config::log_file_flags

pj::LogConfig::decor

pjsua_logging_config::decor

pj::LogConfig::writer (pj::LogWriter)

pjsua_logging_config::cb (function-pointer callback)

(not exposed)

pj_log_set_level(), pj_log_set_decor(), pj_log_set_log_func() (raw PJLIB API)

References