whoami

Lucas Jenß

cat /etc/motd

The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.

Close-up of white dominoes with black dots standing on green felt surface, shallow depth of field, focused mood

ls -la ~/languages/

total 8
drwxr-xr-x
▶ PHP
▶ Ruby
▶ Scala
▶ C#
▶ JavaScript
▶ Objective-C
▶ Shell Scripting

ls -la ~/toolchain/

total 7
drwxr-xr-x
▶ Typo3
▶ Akka
▶ Capistrano
▶ Git
▶ MAMP
▶ Adobe Illustrator
▶ NSTrackingArea (Cocoa)

uname -a

platforms
drwxr-xr-x
▶ Mac OS X
▶ Unix

How to Use the Android NDK to Log Native Calls With Android Logcat

When an Android application crosses from Kotlin or Java into C and C++ code, failures can become difficult to trace. A Java stack trace may show only the JNI boundary, while the real problem is buried in a native function, a bad pointer, an invalid buffer, or an unexpected return value. The Android NDK provides the tools to add useful diagnostics directly inside that native layer.

Android logcat is the most convenient place to collect these messages during development. With the Android logging API, native code can report function entry, arguments, state changes, warnings, and errors using the same system log viewed by Android Studio, adb, or a physical handset. The method works well for projects tested across Australian devices, whether development is taking place in Sydney, Melbourne, Brisbane, or a smaller regional office.

Add Android Logging Support to Native Code

The NDK exposes Android’s logging functions through the android/log.h header. The most common function is __android_log_print, which accepts a priority, a tag, a printf-style format string, and any values required by that format.

#include <android/log.h>

#define LOG_TAG "NativeEngine"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,  LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,  LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

A native function can then log its progress without sending output through Java:

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_engine_NativeBridge_process(JNIEnv* env, jobject object, jint value) {
    LOGD("process() called with value=%d", value);

    if (value < 0) {
        LOGW("Ignoring negative value=%d", value);
        return -1;
    }

    int result = value * 2;
    LOGI("process() returning result=%d", result);
    return result;
}

The tag should identify the native subsystem rather than every individual method. A stable tag such as NativeEngine, ImageDecoder, or CryptoBridge makes filtering easier. Developers maintaining practical troubleshooting notes, such as those collected on native debugging notes, can also use consistent tags to compare output between builds and devices.

Link the Native Library Correctly

Logging code must be compiled and linked with Android’s logging library. Projects using CMake generally add log to target_link_libraries:

cmake_minimum_required(VERSION 3.22.1)
project("nativeengine")

add_library(nativeengine SHARED
    native-lib.cpp
)

find_library(log-lib log)

target_link_libraries(nativeengine
    ${log-lib}
)

The find_library command resolves the platform library, while ${log-lib} passes it to the linker. If this step is missing, compilation may succeed but linking will fail with an undefined reference to __android_log_print or a related logging symbol.

Older NDK projects using Android.mk need a different declaration:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := nativeengine
LOCAL_SRC_FILES := native-lib.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

After changing the build file, rebuild the native module rather than relying on an old .so file in the application. ABI differences matter when testing on an x86 emulator, an arm64 handset, or an older 32-bit device. This is especially relevant when a team in Australia is testing a broad Android market, where budget phones and carrier-specific models can coexist with current flagship hardware.

Read Native Messages With Logcat

The simplest command for viewing native output is:

adb logcat

To display only messages from the selected tag, use:

adb logcat -s NativeEngine

The -s option applies a filter specification and suppresses unrelated messages. You can also set a minimum priority:

adb logcat NativeEngine:D *:S

This shows debug and higher-priority messages from NativeEngine while silencing other tags. The same filter can be entered in Android Studio’s Logcat window, where the connected device and application process can be selected from the interface.

Before collecting logs, clear old output so that a previous crash does not obscure the current test:

adb logcat -c
adb logcat -v threadtime NativeEngine:D *:S

The threadtime format includes the date, time, process ID, thread ID, priority, tag, and message. Those fields are valuable when a native call runs on a worker thread. If a bug appears only during a commute or on a particular handset in Perth, the timestamp and process details can help distinguish an application issue from a stale log.

Make Function Traces Useful

Logging every line of native code creates noise and can reduce performance. A better trace records meaningful boundaries: JNI entry, important parameters, external calls, return values, and failure paths.

LOGD("decodeFrame: width=%d height=%d size=%zu",
     width, height, bufferSize);

int status = decode_frame(buffer, bufferSize, width, height);

if (status != 0) {
    LOGE("decodeFrame failed: status=%d", status);
    return status;
}

LOGI("decodeFrame completed successfully");

Use the correct format specifier. %d is suitable for an int, %u for an unsigned integer, %zu for size_t, and %p for a pointer. Passing the wrong type to a variadic logging function can produce misleading output or undefined behaviour. Never print a pointer as though it were an integer.

For system or library failures, include the relevant error value:

#include <cerrno>
#include <cstring>

FILE* file = fopen(path, "rb");
if (file == nullptr) {
    LOGE("Unable to open '%s': errno=%d (%s)",
         path, errno, strerror(errno));
}

Avoid logging private user data, authentication tokens, health information, or complete documents. Australia’s Privacy Act 1988 and the Australian Privacy Principles make careless handling of personal information a real operational concern. Logs copied into a bug tracker, uploaded from a Melbourne test device, or shared with an overseas support team may outlive the original debugging session.

Control Verbosity Across Build Types

Debug output is useful during development but should be controlled in production. A compile-time switch can remove verbose messages from release builds:

#ifdef NDEBUG
#define LOGD(...) ((void)0)
#else
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#endif

Warnings and errors can remain available when they do not reveal sensitive values. An alternative is to define a project-specific logging wrapper that supports runtime configuration. This makes it possible to enable detailed diagnostics for a test build without changing every native function.

Remember that logcat is not a permanent application log. Android may rotate or discard entries, and access to another application’s logs is restricted on modern Android versions. A production app should use a carefully designed crash-reporting or diagnostic system when persistent evidence is required. Native logging is best for local testing, integration checks, and reproducing a problem under controlled conditions.

The comparison below summarises the main choices:

Logging method Best use Advantages Limitations
__android_log_print Native C and C++ diagnostics Simple, fast to add, visible in logcat Output is temporary and may expose sensitive data
printf or stderr Very early experiments Familiar desktop-style output Capture and formatting are less predictable on Android
Java or Kotlin logger Managed application code Integrates naturally with app-level diagnostics Does not directly show native state
Crash-reporting SDK Production failure analysis Persistent reports and device context Requires configuration, privacy review, and network handling
Debugger breakpoints Interactive investigation Inspect memory and execution state Slower to use and less suitable for intermittent failures

A small, well-filtered native trace often reveals more than a large unstructured dump. Start with the JNI method, record the values crossing the boundary, then add messages around the native operation that fails. Once the cause is clear, reduce or remove diagnostic detail before shipping through Google Play or distributing the application through an Australian business channel.

Add a consistent logging wrapper to your next NDK module, rebuild the correct ABI, and reproduce the problem with adb logcat -s YourTag. That workflow turns an opaque native crash into a sequence of observable calls and gives your future troubleshooting sessions a reliable starting point.


cat ~/interests.json

KeyValue
editorTerminal-first workflow
osMac OS X / Unix
vcsGit, distributed version control
deployCapistrano, cron automation
graphicsSVG, Adobe Illustrator troubleshooting
networkingIP validation, SSH, VPN

git log --oneline --reverse

2013-10-30

Solving SVG import issues in Adobe Illustrator CS6 and CC

When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.

2013

Solving NDK build issues on OS X

Troubleshooting native development kit compilation problems on Mac OS X.

2013

Programmatically adding PHP generated TypoScript to the backend configuration

Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.

2013

ArgumentError: Could not parse PKey: no start line

Debugging an SSH key parsing error encountered during deployment.

2011-08-04

Validating IP-Addresses in PHP

Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.

2011-07-09

Cocoa: Using NSTrackingArea

A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.


cat ~/contact.txt

github: github.com/x3ro
stackoverflow: x3ro
coderwall: coderwall.com/x3ro
twitter: @x3rames