Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"twxs.cmake"
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"sonarlint.pathToCompileCommands": "${workspaceFolder}/build/compile_commands.json"
}
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.11)
cmake_minimum_required(VERSION 2.8.12)
project(logger C)

set(CMAKE_C_FLAGS "-Wall -std=c89")
Expand All @@ -19,6 +19,12 @@ add_library(${PROJECT_NAME} SHARED ${source_files})
add_library(${PROJECT_NAME}_static STATIC ${source_files})
set_target_properties(${PROJECT_NAME}_static PROPERTIES OUTPUT_NAME ${PROJECT_NAME})

# Android NDK log library
if(ANDROID)
target_link_libraries(${PROJECT_NAME} PRIVATE log)
target_link_libraries(${PROJECT_NAME}_static INTERFACE log)
endif()

# Export the include directory
target_include_directories(
${PROJECT_NAME} PUBLIC
Expand Down
41 changes: 41 additions & 0 deletions CUSTOMIZATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Customizations

## CMake

The `cmake_minimum_required` was raised from 2.8.11 to 2.8.12
to avoid the "Compatibility with CMake < 2.8.12 will be removed from a future version of
CMake." deprecation warning.

And unsure about the mechanism, but there is also a nice side-benefit that Visual Studio Code
won't complain about `extern "C" {` in the C headers.

## Visual Studio Code

Various files in `.vscode/` have been committed to support easy Visual Studio Code development.

## Android

### Android Development

To develop with Android, download and unpack the Android NDK. For this example I am using
Android NDK 23.1.7779620.

1. Run `CMake: Edit User-Local CMake Kits`.
2. Add an entry like:

```json
{
"name": "Android NDK 23.1.7779620",
"compilers": {
"C": "/some/path/android-sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android31-clang",
"CXX": "/some/path/android-sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android31-clang++"
},
"isTrusted": true
}
```
3. Run `CMake: Select a Kit` and choose your Android NDK.
4. Run `CMake: Configure`.
5. Run `CMake: Build`. The build may show an error that can be ignored:
> `[proc] The command: x86_64-linux-android31-clang -v failed with error: Error: spawn x86_64-linux-android31-clang ENOENT`

The last three (3) commands you could do from the UI (both the toolbar and the CMake Panel).
88 changes: 88 additions & 0 deletions src/logger.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@
#include <sys/syscall.h>
#include <unistd.h>
#endif /* defined(_WIN32) || defined(_WIN64) */
#if defined(__ANDROID__)
#include <android/log.h>
#endif

enum {
/* Logger type */
kConsoleLogger = 1 << 0,
kFileLogger = 1 << 1,
kAndroidLogger = 1 << 2,

kMaxAndroidTagLen = 63, /* without null character */
kMaxFileNameLen = 255, /* without null character */
kDefaultMaxFileSize = 1048576L, /* 1 MB */
};
Expand All @@ -37,6 +42,14 @@ static struct {
unsigned long long flushedTime;
} s_flog;

#if defined(__ANDROID__)
/* Android logger */
static struct {
int hasTag;
char tag[kMaxAndroidTagLen + 1];
} s_androidlog;
#endif

static volatile int s_logger;
static volatile LogLevel s_logLevel = LogLevel_INFO;
static volatile long s_flushInterval = 0; /* msec, 0 is auto flush off */
Expand Down Expand Up @@ -182,6 +195,26 @@ int logger_initFileLogger(const char* filename, long maxFileSize, unsigned char
return ok;
}

int logger_initAndroid(const char* tag)
{
#if defined(__ANDROID__)
init();
lock();
if (tag == NULL) {
s_androidlog.tag[0] = 0;
s_androidlog.hasTag = 0;
} else {
strncpy(s_androidlog.tag, tag, sizeof(s_androidlog.tag));
s_androidlog.hasTag = 1;
}
s_logger |= kAndroidLogger;
unlock();
return 1;
#else
return 0;
#endif
}

void logger_setLevel(LogLevel level)
{
s_logLevel = level;
Expand Down Expand Up @@ -220,6 +253,7 @@ void logger_flush()
if (hasFlag(s_logger, kFileLogger)) {
fflush(s_flog.output);
}
/* note: Android has no flush() function. */
}

static char getLevelChar(LogLevel level)
Expand All @@ -235,6 +269,21 @@ static char getLevelChar(LogLevel level)
}
}

#if defined(__ANDROID__)
static int getAndroidPriority(LogLevel level)
{
switch (level) {
case LogLevel_TRACE: return ANDROID_LOG_VERBOSE;
case LogLevel_DEBUG: return ANDROID_LOG_DEBUG;
case LogLevel_INFO: return ANDROID_LOG_INFO;
case LogLevel_WARN: return ANDROID_LOG_WARN;
case LogLevel_ERROR: return ANDROID_LOG_ERROR;
case LogLevel_FATAL: return ANDROID_LOG_FATAL;
default: return ANDROID_LOG_DEFAULT;
}
}
#endif

static void getTimestamp(const struct timeval* time, char* timestamp, size_t size)
{
time_t sec = time->tv_sec; /* a necessary variable to avoid a runtime error on Windows */
Expand Down Expand Up @@ -331,6 +380,38 @@ static long vflog(FILE* fp, char levelc, const char* timestamp, long threadID,
return totalsize;
}

#if defined(__ANDROID__)
static long vandroidlog(LogLevel level, const char* file, int line, const char* fmt, va_list arg)
{
int size;
long totalsize = 0;
int prio = getAndroidPriority(level);
const char* tag;
if (s_androidlog.hasTag) {
tag = s_androidlog.tag;
} else {
tag = NULL;
}

/* Maximum Android log message size is "implementation-specific (1023 bytes)"
per <android/log.h>, or 4096 bytes in other online sources. */
char buf_user[4096];
char buf_message[4096];

/* Android logging uses a concept of "message" which is the same as
a c-logger log statement. It is not just an output stream. So we have to
assemble all part of the c-logger log statement into a single log
message for Android. */
vsnprintf(buf_user, sizeof(buf_user), fmt, arg);
snprintf(buf_message, sizeof(buf_message), "%s:%d: %s", file, line, buf_user);

if ((size = __android_log_write(prio, tag, buf_message)) > 0) {
totalsize += size;
}
return totalsize;
}
#endif

void logger_log(LogLevel level, const char* file, int line, const char* fmt, ...)
{
struct timeval now;
Expand Down Expand Up @@ -368,6 +449,13 @@ void logger_log(LogLevel level, const char* file, int line, const char* fmt, ...
va_end(farg);
}
}
#if defined(__ANDROID__)
if (hasFlag(s_logger, kAndroidLogger)) {
va_start(carg, fmt);
vandroidlog(level, file, line, fmt, carg);
va_end(carg);
}
#endif
unlock();
}

19 changes: 19 additions & 0 deletions src/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ int logger_initConsoleLogger(FILE* output);
*/
int logger_initFileLogger(const char* filename, long maxFileSize, unsigned char maxBackupFiles);

/**
* Initialize the logger as an Android logger viewable in Logcat.
*
* The tag usually identifies the class or activity where the log
* call occurs, like "MyActivity". If the tag is NULL, the Android
* default tag (the value of getprogname()) will be used. The default
* tag truncated to the maximum log message size, though appropriate
* tags should be much smaller.
*
* Support for Android requires that the project was built using
* the Android NDK toolchain. An error (0) will be returned if
* Android is not supported.
*
* @param[in] tag The source of a log message.
* @return Non-zero value upon success or 0 on error
*/
int logger_initAndroid(const char* tag);

/**
* Set the log level.
* Message levels lower than this value will be discarded.
Expand Down Expand Up @@ -92,6 +110,7 @@ void logger_flush(void);
* Make sure to call one of the following initialize functions before starting logging.
* - logger_initConsoleLogger()
* - logger_initFileLogger()
* - logger_initAndroid()
*
* @param[in] level A log level
* @param[in] file A file name string
Expand Down