logcat: Create liblogcat
Try to leverage as much of logcat as-is and produce a viable
library API that others can use for their own logcat execution.
Added a test to check ANDROID_PRINTF_LOG environment variable.
Test: gTest logcat-unit-tests
Bug: 35326290
Change-Id: I30de692ea9d83e6fd6e5d9e7cf93d31401a88a40
diff --git a/logcat/Android.mk b/logcat/Android.mk
index 1dacbe1..4b8746c 100644
--- a/logcat/Android.mk
+++ b/logcat/Android.mk
@@ -1,20 +1,32 @@
# Copyright 2006-2014 The Android Open Source Project
-LOCAL_PATH:= $(call my-dir)
+LOCAL_PATH := $(call my-dir)
+
+logcatLibs := liblog libbase libcutils libpcrecpp
+
include $(CLEAR_VARS)
-LOCAL_SRC_FILES:= logcat.cpp event.logtags
-
-LOCAL_SHARED_LIBRARIES := liblog libbase libcutils libpcrecpp
-
LOCAL_MODULE := logcat
-
+LOCAL_SRC_FILES := logcat_main.cpp event.logtags
+LOCAL_SHARED_LIBRARIES := liblogcat $(logcatLibs)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_CFLAGS := -Werror
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
+LOCAL_MODULE := liblogcat
+LOCAL_SRC_FILES := logcat.cpp
+LOCAL_SHARED_LIBRARIES := $(logcatLibs)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDES_DIR := $(LOCAL_PATH)/include
+LOCAL_CFLAGS := -Werror
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
LOCAL_MODULE := logpersist.start
LOCAL_MODULE_TAGS := debug
LOCAL_MODULE_CLASS := EXECUTABLES
diff --git a/logcat/include/log/logcat.h b/logcat/include/log/logcat.h
new file mode 100644
index 0000000..41104fe
--- /dev/null
+++ b/logcat/include/log/logcat.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2005-2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LIBS_LOGCAT_H /* header boilerplate */
+#define _LIBS_LOGCAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE
+#ifndef __ANDROID_API__
+#define __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE 1
+#elif __ANDROID_API__ > 24 /* > Nougat */
+#define __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE 1
+#else
+#define __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE 0
+#endif
+#endif
+
+#if __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE
+
+/* For managing an in-process logcat function, rather than forking/execing
+ *
+ * It also serves as the basis for the logcat command.
+ *
+ * The following C API allows a logcat instance to be created, run
+ * to completion, and then release all the associated resources.
+ */
+
+/*
+ * The opaque context
+ */
+#ifndef __android_logcat_context_defined /* typedef boilerplate */
+#define __android_logcat_context_defined
+typedef struct android_logcat_context_internal* android_logcat_context;
+#endif
+
+/* Creates a context associated with this logcat instance
+ *
+ * Returns a pointer to the context, or a NULL on error.
+ */
+android_logcat_context create_android_logcat();
+
+/* Collects and outputs the logcat data to output and error file descriptors
+ *
+ * Will block, performed in-thread and in-process
+ *
+ * The output file descriptor variable, if greater than or equal to 0, is
+ * where the output (ie: stdout) will be sent. The file descriptor is closed
+ * on android_logcat_destroy which terminates the instance, or when an -f flag
+ * (output redirect to a file) is present in the command. The error file
+ * descriptor variable, if greater than or equal to 0, is where the error
+ * stream (ie: stderr) will be sent, also closed on android_logcat_destroy.
+ * The error file descriptor can be set to equal to the output file descriptor,
+ * which will mix output and error stream content, and will defer closure of
+ * the file descriptor on -f flag redirection. Negative values for the file
+ * descriptors will use stdout and stderr FILE references respectively
+ * internally, and will not close the references as noted above.
+ *
+ * Return value is 0 for success, non-zero for errors.
+ */
+int android_logcat_run_command(android_logcat_context ctx, int output, int error,
+ int argc, char* const* argv, char* const* envp);
+
+/* Finished with context
+ *
+ * Free up all associated resources.
+ *
+ * Return value is the result of the android_logcat_run_command, or
+ * non-zero for any errors.
+ */
+int android_logcat_destroy(android_logcat_context* ctx);
+
+#endif /* __ANDROID_USE_LIBLOG_LOGCAT_INTERFACE */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBS_LOGCAT_H */
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index adedf88..b048289 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006-2015 The Android Open Source Project
+ * Copyright (C) 2006-2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@
#include <getopt.h>
#include <math.h>
#include <sched.h>
-#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -45,6 +44,7 @@
#include <cutils/sched_policy.h>
#include <cutils/sockets.h>
#include <log/event_tag_map.h>
+#include <log/logcat.h>
#include <log/logprint.h>
#include <private/android_logger.h>
#include <system/thread_defs.h>
@@ -53,7 +53,59 @@
#define DEFAULT_MAX_ROTATED_LOGS 4
-static AndroidLogFormat* g_logformat;
+struct android_logcat_context_internal {
+ // status
+ int retval;
+ // Arguments passed in
+ int argc;
+ char* const* argv;
+ char* const* envp;
+ int output_fd;
+ int error_fd;
+
+ // library
+ FILE* output; // everything writes to fileno(output), buffer unused
+ FILE* error; // unless error == output.
+ bool stop; // quick exit flag
+ bool stderr_null; // shell "2>/dev/null"
+ bool stderr_stdout; // shell "2>&1"
+
+ // global variables
+ AndroidLogFormat* logformat;
+ const char* outputFileName;
+ // 0 means "no log rotation"
+ size_t logRotateSizeKBytes;
+ // 0 means "unbounded"
+ size_t maxRotatedLogs;
+ size_t outByteCount;
+ int printBinary;
+ int devCount; // >1 means multiple
+ pcrecpp::RE* regex;
+ // 0 means "infinite"
+ size_t maxCount;
+ size_t printCount;
+ bool printItAnyways;
+ bool debug;
+
+ // static variables
+ bool hasOpenedEventTagMap;
+ EventTagMap* eventTagMap;
+};
+
+// Creates a context associated with this logcat instance
+android_logcat_context create_android_logcat() {
+ android_logcat_context_internal* context;
+
+ context = (android_logcat_context_internal*)calloc(
+ 1, sizeof(android_logcat_context_internal));
+ if (!context) return NULL;
+
+ context->output_fd = -1;
+ context->error_fd = -1;
+ context->maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
+
+ return (android_logcat_context)context;
+}
// logd prefixes records with a length field
#define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t)
@@ -79,62 +131,109 @@
namespace android {
-// Global Variables
-
-static const char* g_outputFileName;
-// 0 means "no log rotation"
-static size_t g_logRotateSizeKBytes;
-// 0 means "unbounded"
-static size_t g_maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS;
-static int g_outFD = -1;
-static size_t g_outByteCount;
-static int g_printBinary;
-static int g_devCount; // >1 means multiple
-static pcrecpp::RE* g_regex;
-// 0 means "infinite"
-static size_t g_maxCount;
-static size_t g_printCount;
-static bool g_printItAnyways;
-static bool g_debug;
-
enum helpType { HELP_FALSE, HELP_TRUE, HELP_FORMAT };
// if showHelp is set, newline required in fmt statement to transition to usage
-__noreturn static void logcat_panic(enum helpType showHelp, const char* fmt,
- ...) __printflike(2, 3);
+static void logcat_panic(android_logcat_context_internal* context,
+ enum helpType showHelp, const char* fmt, ...)
+ __printflike(3, 4);
static int openLogFile(const char* pathname) {
return open(pathname, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
}
-static void rotateLogs() {
+static void close_output(android_logcat_context_internal* context) {
+ // split output_from_error
+ if (context->error == context->output) {
+ context->output = NULL;
+ context->output_fd = -1;
+ }
+ if (context->error && (context->output_fd == fileno(context->error))) {
+ context->output_fd = -1;
+ }
+ if (context->output_fd == context->error_fd) {
+ context->output_fd = -1;
+ }
+ // close output channel
+ if (context->output) {
+ if (context->output != stdout) {
+ if (context->output_fd == fileno(context->output)) {
+ context->output_fd = -1;
+ }
+ fclose(context->output);
+ }
+ context->output = NULL;
+ }
+ if (context->output_fd >= 0) {
+ if (context->output_fd != fileno(stdout)) {
+ close(context->output_fd);
+ }
+ context->output_fd = -1;
+ }
+}
+
+static void close_error(android_logcat_context_internal* context) {
+ // split error_from_output
+ if (context->output == context->error) {
+ context->error = NULL;
+ context->error_fd = -1;
+ }
+ if (context->output && (context->error_fd == fileno(context->output))) {
+ context->error_fd = -1;
+ }
+ if (context->error_fd == context->output_fd) {
+ context->error_fd = -1;
+ }
+ // close error channel
+ if (context->error) {
+ if ((context->error != stderr) && (context->error != stdout)) {
+ if (context->error_fd == fileno(context->error)) {
+ context->error_fd = -1;
+ }
+ fclose(context->error);
+ }
+ context->error = NULL;
+ }
+ if (context->error_fd >= 0) {
+ if ((context->error_fd != fileno(stdout)) &&
+ (context->error_fd != fileno(stderr))) {
+ close(context->error_fd);
+ }
+ context->error_fd = -1;
+ }
+}
+
+static void rotateLogs(android_logcat_context_internal* context) {
int err;
// Can't rotate logs if we're not outputting to a file
- if (g_outputFileName == NULL) {
+ if (context->outputFileName == NULL) {
return;
}
- close(g_outFD);
+ close_output(context);
// Compute the maximum number of digits needed to count up to
- // g_maxRotatedLogs in decimal. eg:
- // g_maxRotatedLogs == 30
+ // maxRotatedLogs in decimal. eg:
+ // maxRotatedLogs == 30
// -> log10(30) == 1.477
// -> maxRotationCountDigits == 2
int maxRotationCountDigits =
- (g_maxRotatedLogs > 0) ? (int)(floor(log10(g_maxRotatedLogs) + 1)) : 0;
+ (context->maxRotatedLogs > 0)
+ ? (int)(floor(log10(context->maxRotatedLogs) + 1))
+ : 0;
- for (int i = g_maxRotatedLogs; i > 0; i--) {
+ for (int i = context->maxRotatedLogs; i > 0; i--) {
std::string file1 = android::base::StringPrintf(
- "%s.%.*d", g_outputFileName, maxRotationCountDigits, i);
+ "%s.%.*d", context->outputFileName, maxRotationCountDigits, i);
std::string file0;
if (i - 1 == 0) {
- file0 = android::base::StringPrintf("%s", g_outputFileName);
+ file0 = android::base::StringPrintf("%s", context->outputFileName);
} else {
- file0 = android::base::StringPrintf("%s.%.*d", g_outputFileName,
- maxRotationCountDigits, i - 1);
+ file0 =
+ android::base::StringPrintf("%s.%.*d", context->outputFileName,
+ maxRotationCountDigits, i - 1);
}
if ((file0.length() == 0) || (file1.length() == 0)) {
@@ -149,109 +248,109 @@
}
}
- g_outFD = openLogFile(g_outputFileName);
+ context->output_fd = openLogFile(context->outputFileName);
- if (g_outFD < 0) {
- logcat_panic(HELP_FALSE, "couldn't open output file");
+ if (context->output_fd < 0) {
+ logcat_panic(context, HELP_FALSE, "couldn't open output file");
+ return;
}
+ context->output = fdopen(context->output_fd, "web");
- g_outByteCount = 0;
+ context->outByteCount = 0;
}
-void printBinary(struct log_msg* buf) {
+void printBinary(android_logcat_context_internal* context, struct log_msg* buf) {
size_t size = buf->len();
- TEMP_FAILURE_RETRY(write(g_outFD, buf, size));
+ TEMP_FAILURE_RETRY(write(context->output_fd, buf, size));
}
-static bool regexOk(const AndroidLogEntry& entry) {
- if (!g_regex) {
+static bool regexOk(android_logcat_context_internal* context,
+ const AndroidLogEntry& entry) {
+ if (!context->regex) {
return true;
}
std::string messageString(entry.message, entry.messageLen);
- return g_regex->PartialMatch(messageString);
+ return context->regex->PartialMatch(messageString);
}
-static void processBuffer(log_device_t* dev, struct log_msg* buf) {
+static void processBuffer(android_logcat_context_internal* context,
+ log_device_t* dev, struct log_msg* buf) {
int bytesWritten = 0;
int err;
AndroidLogEntry entry;
char binaryMsgBuf[1024];
if (dev->binary) {
- static bool hasOpenedEventTagMap = false;
- static EventTagMap* eventTagMap = NULL;
-
- if (!eventTagMap && !hasOpenedEventTagMap) {
- eventTagMap = android_openEventTagMap(NULL);
- hasOpenedEventTagMap = true;
+ if (!context->eventTagMap && !context->hasOpenedEventTagMap) {
+ context->eventTagMap = android_openEventTagMap(NULL);
+ context->hasOpenedEventTagMap = true;
}
- err = android_log_processBinaryLogBuffer(&buf->entry_v1, &entry,
- eventTagMap, binaryMsgBuf,
- sizeof(binaryMsgBuf));
+ err = android_log_processBinaryLogBuffer(
+ &buf->entry_v1, &entry, context->eventTagMap, binaryMsgBuf,
+ sizeof(binaryMsgBuf));
// printf(">>> pri=%d len=%d msg='%s'\n",
// entry.priority, entry.messageLen, entry.message);
} else {
err = android_log_processLogBuffer(&buf->entry_v1, &entry);
}
- if ((err < 0) && !g_debug) {
- goto error;
+ if ((err < 0) && !context->debug) {
+ return;
}
if (android_log_shouldPrintLine(
- g_logformat, std::string(entry.tag, entry.tagLen).c_str(),
+ context->logformat, std::string(entry.tag, entry.tagLen).c_str(),
entry.priority)) {
- bool match = regexOk(entry);
+ bool match = regexOk(context, entry);
- g_printCount += match;
- if (match || g_printItAnyways) {
- bytesWritten =
- android_log_printLogLine(g_logformat, g_outFD, &entry);
+ context->printCount += match;
+ if (match || context->printItAnyways) {
+ bytesWritten = android_log_printLogLine(context->logformat,
+ context->output_fd, &entry);
if (bytesWritten < 0) {
- logcat_panic(HELP_FALSE, "output error");
+ logcat_panic(context, HELP_FALSE, "output error");
+ return;
}
}
}
- g_outByteCount += bytesWritten;
+ context->outByteCount += bytesWritten;
- if (g_logRotateSizeKBytes > 0 &&
- (g_outByteCount / 1024) >= g_logRotateSizeKBytes) {
- rotateLogs();
+ if (context->logRotateSizeKBytes > 0 &&
+ (context->outByteCount / 1024) >= context->logRotateSizeKBytes) {
+ rotateLogs(context);
}
-
-error:
- return;
}
-static void maybePrintStart(log_device_t* dev, bool printDividers) {
+static void maybePrintStart(android_logcat_context_internal* context,
+ log_device_t* dev, bool printDividers) {
if (!dev->printed || printDividers) {
- if (g_devCount > 1 && !g_printBinary) {
+ if (context->devCount > 1 && !context->printBinary) {
char buf[1024];
snprintf(buf, sizeof(buf), "--------- %s %s\n",
dev->printed ? "switch to" : "beginning of", dev->device);
- if (write(g_outFD, buf, strlen(buf)) < 0) {
- logcat_panic(HELP_FALSE, "output error");
+ if (write(context->output_fd, buf, strlen(buf)) < 0) {
+ logcat_panic(context, HELP_FALSE, "output error");
+ return;
}
}
dev->printed = true;
}
}
-static void setupOutputAndSchedulingPolicy(bool blocking) {
- if (g_outputFileName == NULL) {
- g_outFD = STDOUT_FILENO;
- return;
- }
+static void setupOutputAndSchedulingPolicy(
+ android_logcat_context_internal* context, bool blocking) {
+ if (context->outputFileName == NULL) return;
if (blocking) {
// Lower priority and set to batch scheduling if we are saving
// the logs into files and taking continuous content.
- if (set_sched_policy(0, SP_BACKGROUND) < 0) {
- fprintf(stderr, "failed to set background scheduling policy\n");
+ if ((set_sched_policy(0, SP_BACKGROUND) < 0) && context->error) {
+ fprintf(context->error,
+ "failed to set background scheduling policy\n");
}
struct sched_param param;
@@ -260,36 +359,49 @@
fprintf(stderr, "failed to set to batch scheduler\n");
}
- if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
- fprintf(stderr, "failed set to priority\n");
+ if ((setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) &&
+ context->error) {
+ fprintf(context->error, "failed set to priority\n");
}
}
- g_outFD = openLogFile(g_outputFileName);
+ close_output(context);
- if (g_outFD < 0) {
- logcat_panic(HELP_FALSE, "couldn't open output file");
+ context->output_fd = openLogFile(context->outputFileName);
+
+ if (context->output_fd < 0) {
+ logcat_panic(context, HELP_FALSE, "couldn't open output file");
+ return;
}
struct stat statbuf;
- if (fstat(g_outFD, &statbuf) == -1) {
- close(g_outFD);
- logcat_panic(HELP_FALSE, "couldn't get output file stat\n");
+ if (fstat(context->output_fd, &statbuf) == -1) {
+ close_output(context);
+ logcat_panic(context, HELP_FALSE, "couldn't get output file stat\n");
+ return;
}
if ((size_t)statbuf.st_size > SIZE_MAX || statbuf.st_size < 0) {
- close(g_outFD);
- logcat_panic(HELP_FALSE, "invalid output file stat\n");
+ close_output(context);
+ logcat_panic(context, HELP_FALSE, "invalid output file stat\n");
+ return;
}
- g_outByteCount = statbuf.st_size;
+ context->output = fdopen(context->output_fd, "web");
+
+ context->outByteCount = statbuf.st_size;
}
// clang-format off
-static void show_help(const char* cmd) {
- fprintf(stderr, "Usage: %s [options] [filterspecs]\n", cmd);
+static void show_help(android_logcat_context_internal* context) {
+ if (!context->error) return;
- fprintf(stderr, "options include:\n"
+ const char* cmd = strrchr(context->argv[0], '/');
+ cmd = cmd ? cmd + 1 : context->argv[0];
+
+ fprintf(context->error, "Usage: %s [options] [filterspecs]\n", cmd);
+
+ fprintf(context->error, "options include:\n"
" -s Set default filter to silent. Equivalent to filterspec '*:S'\n"
" -f <file>, --file=<file> Log to file. Default is stdout\n"
" -r <kbytes>, --rotate-kbytes=<kbytes>\n"
@@ -353,7 +465,7 @@
" comes first. Improves efficiency of polling by providing\n"
" an about-to-wrap wakeup.\n");
- fprintf(stderr, "\nfilterspecs are a series of \n"
+ fprintf(context->error, "\nfilterspecs are a series of \n"
" <tag>[:priority]\n\n"
"where <tag> is a log component tag (or * for all) and priority is:\n"
" V Verbose (default for <tag>)\n"
@@ -371,8 +483,9 @@
"or defaults to \"threadtime\"\n\n");
}
-static void show_format_help() {
- fprintf(stderr,
+static void show_format_help(android_logcat_context_internal* context) {
+ if (!context->error) return;
+ fprintf(context->error,
"-v <format>, --format=<format> options:\n"
" Sets log print format verb and adverbs, where <format> is:\n"
" brief long process raw tag thread threadtime time\n"
@@ -405,7 +518,8 @@
}
// clang-format on
-static int setLogFormat(const char* formatString) {
+static int setLogFormat(android_logcat_context_internal* context,
+ const char* formatString) {
AndroidLogPrintFormat format;
format = android_log_formatFromString(formatString);
@@ -415,7 +529,7 @@
return -1;
}
- return android_log_setPrintFormat(g_logformat, format);
+ return android_log_setPrintFormat(context->logformat, format);
}
static const char multipliers[][2] = { { "" }, { "K" }, { "M" }, { "G" } };
@@ -460,25 +574,32 @@
return true;
}
-static void logcat_panic(enum helpType showHelp, const char* fmt, ...) {
+static void logcat_panic(android_logcat_context_internal* context,
+ enum helpType showHelp, const char* fmt, ...) {
+ context->retval = EXIT_FAILURE;
+ if (!context->error) {
+ context->stop = true;
+ return;
+ }
+
va_list args;
va_start(args, fmt);
- vfprintf(stderr, fmt, args);
+ vfprintf(context->error, fmt, args);
va_end(args);
switch (showHelp) {
case HELP_TRUE:
- show_help(getprogname());
+ show_help(context);
break;
case HELP_FORMAT:
- show_format_help();
+ show_format_help(context);
break;
case HELP_FALSE:
default:
break;
}
- exit(EXIT_FAILURE);
+ context->stop = true;
}
static char* parseTime(log_time& t, const char* cp) {
@@ -576,6 +697,16 @@
return retval;
}
+const char* getenv(android_logcat_context_internal* context, const char* name) {
+ if (!context->envp || !name || !*name) return NULL;
+
+ for (size_t len = strlen(name), i = 0; context->envp[i]; ++i) {
+ if (strncmp(context->envp[i], name, len)) continue;
+ if (context->envp[i][len] == '=') return &context->envp[i][len + 1];
+ }
+ return NULL;
+}
+
} // namespace android
void reportErrorName(const char** current, const char* name,
@@ -589,7 +720,7 @@
*current = name;
}
-int main(int argc, char** argv) {
+static int __logcat(android_logcat_context_internal* context) {
using namespace android;
int err;
int hasSetLogFormat = 0;
@@ -612,15 +743,87 @@
size_t pid = 0;
bool got_t = false;
- signal(SIGPIPE, exit);
+ // object instantiations before goto's can happen
+ log_device_t unexpected("unexpected", false);
+ const char* openDeviceFail = NULL;
+ const char* clearFail = NULL;
+ const char* setSizeFail = NULL;
+ const char* getSizeFail = NULL;
+ int argc = context->argc;
+ char* const* argv = context->argv;
- g_logformat = android_log_format_new();
+ context->output = stdout;
+ context->error = stderr;
- if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
- show_help(argv[0]);
- return EXIT_SUCCESS;
+ for (int i = 0; i < argc; ++i) {
+ // Simulate shell stderr redirect parsing
+ if ((argv[i][0] != '2') || (argv[i][1] != '>')) continue;
+
+ size_t skip = (argv[i][2] == '>') + 2;
+ if (!strcmp(&argv[i][skip], "/dev/null")) {
+ context->stderr_null = true;
+ } else if (!strcmp(&argv[i][skip], "&1")) {
+ context->stderr_stdout = true;
+ } else {
+ // stderr file redirections are not supported
+ fprintf(context->stderr_stdout ? stdout : stderr,
+ "stderr redirection to file %s unsupported, skipping\n",
+ &argv[i][skip]);
+ }
}
+ // Deal with setting up file descriptors and FILE pointers
+ if (context->error_fd >= 0) {
+ if (context->error_fd == context->output_fd) {
+ context->stderr_stdout = true;
+ } else if (context->stderr_null) {
+ close(context->error_fd);
+ context->error_fd = -1;
+ } else {
+ context->error = fdopen(context->error_fd, "web");
+ if (!context->error) {
+ context->retval = -errno;
+ fprintf(context->stderr_stdout ? stdout : stderr,
+ "Failed to fdopen(error_fd=%d) %s\n", context->error_fd,
+ strerror(errno));
+ goto exit;
+ }
+ }
+ }
+ if (context->output_fd >= 0) {
+ context->output = fdopen(context->output_fd, "web");
+ if (!context->output) {
+ context->retval = -errno;
+ fprintf(context->stderr_stdout ? stdout : context->error,
+ "Failed to fdopen(output_fd=%d) %s\n", context->output_fd,
+ strerror(errno));
+ goto exit;
+ }
+ }
+ if (context->stderr_stdout) context->error = context->output;
+ if (context->stderr_null) {
+ context->error_fd = -1;
+ context->error = NULL;
+ }
+ // Only happens if output=stdout
+ if ((context->output_fd < 0) && context->output) {
+ context->output_fd = fileno(context->output);
+ }
+ // Only happens if error=stdout || error=stderr
+ if ((context->error_fd < 0) && context->error) {
+ context->error_fd = fileno(context->error);
+ }
+
+ context->logformat = android_log_format_new();
+
+ if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
+ show_help(context);
+ context->retval = EXIT_SUCCESS;
+ goto exit;
+ }
+
+ // danger: getopt is _not_ reentrant
+ optind = 1;
for (;;) {
int ret;
@@ -677,8 +880,9 @@
if (long_options[option_index].name == pid_str) {
// ToDo: determine runtime PID_MAX?
if (!getSizeTArg(optarg, &pid, 1)) {
- logcat_panic(HELP_TRUE, "%s %s out of range\n",
+ logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
long_options[option_index].name, optarg);
+ goto exit;
}
break;
}
@@ -688,11 +892,13 @@
// ToDo: implement API that supports setting a wrap timeout
size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
if (optarg && !getSizeTArg(optarg, &dummy, 1)) {
- logcat_panic(HELP_TRUE, "%s %s out of range\n",
+ logcat_panic(context, HELP_TRUE, "%s %s out of range\n",
long_options[option_index].name, optarg);
+ goto exit;
}
- if (dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) {
- fprintf(stderr,
+ if ((dummy != ANDROID_LOG_WRAP_DEFAULT_TIMEOUT) &&
+ context->error) {
+ fprintf(context->error,
"WARNING: %s %u seconds, ignoring %zu\n",
long_options[option_index].name,
ANDROID_LOG_WRAP_DEFAULT_TIMEOUT, dummy);
@@ -700,11 +906,11 @@
break;
}
if (long_options[option_index].name == print_str) {
- g_printItAnyways = true;
+ context->printItAnyways = true;
break;
}
if (long_options[option_index].name == debug_str) {
- g_debug = true;
+ context->debug = true;
break;
}
if (long_options[option_index].name == id_str) {
@@ -715,7 +921,7 @@
case 's':
// default to all silent
- android_log_addFilterRule(g_logformat, "*:s");
+ android_log_addFilterRule(context->logformat, "*:s");
break;
case 'c':
@@ -740,23 +946,29 @@
if (strspn(optarg, "0123456789") != strlen(optarg)) {
char* cp = parseTime(tail_time, optarg);
if (!cp) {
- logcat_panic(HELP_FALSE,
+ logcat_panic(context, HELP_FALSE,
"-%c \"%s\" not in time format\n", ret,
optarg);
+ goto exit;
}
if (*cp) {
char c = *cp;
*cp = '\0';
- fprintf(stderr,
+ if (context->error) {
+ fprintf(
+ context->error,
"WARNING: -%c \"%s\"\"%c%s\" time truncated\n",
ret, optarg, c, cp + 1);
+ }
*cp = c;
}
} else {
if (!getSizeTArg(optarg, &tail_lines, 1)) {
- fprintf(stderr,
- "WARNING: -%c %s invalid, setting to 1\n", ret,
- optarg);
+ if (context->error) {
+ fprintf(context->error,
+ "WARNING: -%c %s invalid, setting to 1\n",
+ ret, optarg);
+ }
tail_lines = 1;
}
}
@@ -767,16 +979,17 @@
break;
case 'e':
- g_regex = new pcrecpp::RE(optarg);
+ context->regex = new pcrecpp::RE(optarg);
break;
case 'm': {
char* end = NULL;
- if (!getSizeTArg(optarg, &g_maxCount)) {
- logcat_panic(
- HELP_FALSE,
- "-%c \"%s\" isn't an integer greater than zero\n", ret,
- optarg);
+ if (!getSizeTArg(optarg, &context->maxCount)) {
+ logcat_panic(context, HELP_FALSE,
+ "-%c \"%s\" isn't an "
+ "integer greater than zero\n",
+ ret, optarg);
+ goto exit;
}
} break;
@@ -816,7 +1029,9 @@
}
if (!setLogSize) {
- logcat_panic(HELP_FALSE, "ERROR: -G <num><multiplier>\n");
+ logcat_panic(context, HELP_FALSE,
+ "ERROR: -G <num><multiplier>\n");
+ goto exit;
}
} break;
@@ -845,8 +1060,9 @@
const char* name = android_log_id_to_name(log_id);
if (strcmp(name, optarg) != 0) {
- logcat_panic(HELP_TRUE, "unknown buffer %s\n",
- optarg);
+ logcat_panic(context, HELP_TRUE,
+ "unknown buffer %s\n", optarg);
+ goto exit;
}
if (log_id == LOG_ID_SECURITY) allSelected = false;
idMask |= (1 << log_id);
@@ -889,12 +1105,12 @@
} else {
devices = dev = d;
}
- g_devCount++;
+ context->devCount++;
}
} break;
case 'B':
- g_printBinary = 1;
+ context->printBinary = 1;
break;
case 'f':
@@ -902,32 +1118,36 @@
tail_time = lastLogTime(optarg);
}
// redirect output to a file
- g_outputFileName = optarg;
+ context->outputFileName = optarg;
break;
case 'r':
- if (!getSizeTArg(optarg, &g_logRotateSizeKBytes, 1)) {
- logcat_panic(HELP_TRUE, "Invalid parameter \"%s\" to -r\n",
- optarg);
+ if (!getSizeTArg(optarg, &context->logRotateSizeKBytes, 1)) {
+ logcat_panic(context, HELP_TRUE,
+ "Invalid parameter \"%s\" to -r\n", optarg);
+ goto exit;
}
break;
case 'n':
- if (!getSizeTArg(optarg, &g_maxRotatedLogs, 1)) {
- logcat_panic(HELP_TRUE, "Invalid parameter \"%s\" to -n\n",
- optarg);
+ if (!getSizeTArg(optarg, &context->maxRotatedLogs, 1)) {
+ logcat_panic(context, HELP_TRUE,
+ "Invalid parameter \"%s\" to -n\n", optarg);
+ goto exit;
}
break;
case 'v':
if (!strcmp(optarg, "help") || !strcmp(optarg, "--help")) {
- show_format_help();
- exit(0);
+ show_format_help(context);
+ context->retval = EXIT_SUCCESS;
+ goto exit;
}
- err = setLogFormat(optarg);
+ err = setLogFormat(context, optarg);
if (err < 0) {
- logcat_panic(HELP_FORMAT,
+ logcat_panic(context, HELP_FORMAT,
"Invalid parameter \"%s\" to -v\n", optarg);
+ goto exit;
}
hasSetLogFormat |= err;
break;
@@ -947,7 +1167,10 @@
const char* logcat = strstr(cmdline.c_str(), KERNEL_OPTION);
// if nothing found or invalid filters, exit quietly
- if (!logcat) return EXIT_SUCCESS;
+ if (!logcat) {
+ context->retval = EXIT_SUCCESS;
+ goto exit;
+ }
const char* p = logcat + strlen(KERNEL_OPTION);
const char* q = strpbrk(p, " \t\n\r");
@@ -965,16 +1188,22 @@
std::string devname = "/dev/" + std::string(p, len);
cmdline.erase();
- fprintf(stderr, "logcat using %s\n", devname.c_str());
+ if (context->error) {
+ fprintf(context->error, "logcat using %s\n",
+ devname.c_str());
+ }
- int fd = open(devname.c_str(), O_WRONLY);
+ FILE* fp = fopen(devname.c_str(), "web");
devname.erase();
- if (fd < 0) break;
+ if (!fp) break;
// close output and error channels, replace with console
- dup2(fd, 1);
- dup2(fd, 2);
- close(fd);
+ android::close_output(context);
+ android::close_error(context);
+ context->stderr_stdout = true;
+ context->output = fp;
+ context->error = context->output;
+ if (context->stderr_null) context->error = NULL;
}
break;
@@ -983,56 +1212,62 @@
break;
case ':':
- logcat_panic(HELP_TRUE, "Option -%c needs an argument\n",
- optopt);
- break;
+ logcat_panic(context, HELP_TRUE,
+ "Option -%c needs an argument\n", optopt);
+ goto exit;
default:
- logcat_panic(HELP_TRUE, "Unrecognized Option %c\n", optopt);
- break;
+ logcat_panic(context, HELP_TRUE, "Unrecognized Option %c\n",
+ optopt);
+ goto exit;
}
}
- if (g_maxCount && got_t) {
- logcat_panic(HELP_TRUE,
+ if (context->maxCount && got_t) {
+ logcat_panic(context, HELP_TRUE,
"Cannot use -m (--max-count) and -t together\n");
+ goto exit;
}
- if (g_printItAnyways && (!g_regex || !g_maxCount)) {
+ if (context->printItAnyways && (!context->regex || !context->maxCount)) {
// One day it would be nice if --print -v color and --regex <expr>
// could play with each other and show regex highlighted content.
// clang-format off
- fprintf(stderr, "WARNING: "
+ if (context->error) {
+ fprintf(context->error, "WARNING: "
"--print ignored, to be used in combination with\n"
- " "
+ " "
"--regex <expr> and --max-count <N>\n");
- // clang-format on
- g_printItAnyways = false;
+ }
+ context->printItAnyways = false;
}
if (!devices) {
dev = devices = new log_device_t("main", false);
- g_devCount = 1;
+ context->devCount = 1;
if (android_name_to_log_id("system") == LOG_ID_SYSTEM) {
dev = dev->next = new log_device_t("system", false);
- g_devCount++;
+ context->devCount++;
}
if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
dev = dev->next = new log_device_t("crash", false);
- g_devCount++;
+ context->devCount++;
}
}
- if (g_logRotateSizeKBytes != 0 && g_outputFileName == NULL) {
- logcat_panic(HELP_TRUE, "-r requires -f as well\n");
+ if (context->logRotateSizeKBytes != 0 && context->outputFileName == NULL) {
+ logcat_panic(context, HELP_TRUE, "-r requires -f as well\n");
+ goto exit;
}
if (setId != NULL) {
- if (g_outputFileName == NULL) {
- logcat_panic(HELP_TRUE, "--id='%s' requires -f as well\n", setId);
+ if (context->outputFileName == NULL) {
+ logcat_panic(context, HELP_TRUE,
+ "--id='%s' requires -f as well\n", setId);
+ goto exit;
}
- std::string file_name =
- android::base::StringPrintf("%s.id", g_outputFileName);
+ std::string file_name = android::base::StringPrintf(
+ "%s.id", context->outputFileName);
std::string file;
bool file_ok = android::base::ReadFileToString(file_name, &file);
android::base::WriteStringToFile(setId, file_name, S_IRUSR | S_IWUSR,
@@ -1043,45 +1278,53 @@
}
if (hasSetLogFormat == 0) {
- const char* logFormat = getenv("ANDROID_PRINTF_LOG");
+ const char* logFormat = android::getenv(context, "ANDROID_PRINTF_LOG");
if (logFormat != NULL) {
- err = setLogFormat(logFormat);
- if (err < 0) {
- fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n",
+ err = setLogFormat(context, logFormat);
+ if ((err < 0) && context->error) {
+ fprintf(context->error,
+ "invalid format in ANDROID_PRINTF_LOG '%s'\n",
logFormat);
}
} else {
- setLogFormat("threadtime");
+ setLogFormat(context, "threadtime");
}
}
if (forceFilters.size()) {
- err = android_log_addFilterString(g_logformat, forceFilters.c_str());
+ err = android_log_addFilterString(context->logformat,
+ forceFilters.c_str());
if (err < 0) {
- logcat_panic(HELP_FALSE,
+ logcat_panic(context, HELP_FALSE,
"Invalid filter expression in logcat args\n");
+ goto exit;
}
} else if (argc == optind) {
// Add from environment variable
- char* env_tags_orig = getenv("ANDROID_LOG_TAGS");
+ const char* env_tags_orig = android::getenv(context, "ANDROID_LOG_TAGS");
if (env_tags_orig != NULL) {
- err = android_log_addFilterString(g_logformat, env_tags_orig);
+ err = android_log_addFilterString(context->logformat,
+ env_tags_orig);
if (err < 0) {
- logcat_panic(HELP_TRUE,
- "Invalid filter expression in ANDROID_LOG_TAGS\n");
+ logcat_panic(context, HELP_TRUE,
+ "Invalid filter expression in ANDROID_LOG_TAGS\n");
+ goto exit;
}
}
} else {
// Add from commandline
- for (int i = optind; i < argc; i++) {
- err = android_log_addFilterString(g_logformat, argv[i]);
+ for (int i = optind ; i < argc ; i++) {
+ // skip stderr redirections of _all_ kinds
+ if ((argv[i][0] == '2') && (argv[i][1] == '>')) continue;
+ err = android_log_addFilterString(context->logformat, argv[i]);
if (err < 0) {
- logcat_panic(HELP_TRUE, "Invalid filter expression '%s'\n",
- argv[i]);
+ logcat_panic(context, HELP_TRUE,
+ "Invalid filter expression '%s'\n", argv[i]);
+ goto exit;
}
}
}
@@ -1092,10 +1335,6 @@
} else {
logger_list = android_logger_list_alloc(mode, tail_lines, pid);
}
- const char* openDeviceFail = NULL;
- const char* clearFail = NULL;
- const char* setSizeFail = NULL;
- const char* getSizeFail = NULL;
// We have three orthogonal actions below to clear, set log size and
// get log size. All sharing the same iteration loop.
while (dev) {
@@ -1109,22 +1348,21 @@
}
if (clearLog || setId) {
- if (g_outputFileName) {
+ if (context->outputFileName) {
int maxRotationCountDigits =
- (g_maxRotatedLogs > 0)
- ? (int)(floor(log10(g_maxRotatedLogs) + 1))
- : 0;
+ (context->maxRotatedLogs > 0) ?
+ (int)(floor(log10(context->maxRotatedLogs) + 1)) :
+ 0;
- for (int i = g_maxRotatedLogs; i >= 0; --i) {
+ for (int i = context->maxRotatedLogs ; i >= 0 ; --i) {
std::string file;
if (i == 0) {
- file =
- android::base::StringPrintf("%s", g_outputFileName);
- } else {
file = android::base::StringPrintf(
- "%s.%.*d", g_outputFileName, maxRotationCountDigits,
- i);
+ "%s", context->outputFileName);
+ } else {
+ file = android::base::StringPrintf("%s.%.*d",
+ context->outputFileName, maxRotationCountDigits, i);
}
if (file.length() == 0) {
@@ -1158,32 +1396,44 @@
if ((size < 0) || (readable < 0)) {
reportErrorName(&getSizeFail, dev->device, allSelected);
} else {
- printf(
- "%s: ring buffer is %ld%sb (%ld%sb consumed),"
- " max entry is %db, max payload is %db\n",
- dev->device, value_of_size(size), multiplier_of_size(size),
- value_of_size(readable), multiplier_of_size(readable),
- (int)LOGGER_ENTRY_MAX_LEN, (int)LOGGER_ENTRY_MAX_PAYLOAD);
+ std::string str = android::base::StringPrintf(
+ "%s: ring buffer is %ld%sb (%ld%sb consumed),"
+ " max entry is %db, max payload is %db\n",
+ dev->device,
+ value_of_size(size), multiplier_of_size(size),
+ value_of_size(readable), multiplier_of_size(readable),
+ (int)LOGGER_ENTRY_MAX_LEN,
+ (int)LOGGER_ENTRY_MAX_PAYLOAD);
+ TEMP_FAILURE_RETRY(write(context->output_fd,
+ str.data(), str.length()));
}
}
dev = dev->next;
}
+
+ context->retval = EXIT_SUCCESS;
+
// report any errors in the above loop and exit
if (openDeviceFail) {
- logcat_panic(HELP_FALSE, "Unable to open log device '%s'\n",
- openDeviceFail);
+ logcat_panic(context, HELP_FALSE,
+ "Unable to open log device '%s'\n", openDeviceFail);
+ goto close;
}
if (clearFail) {
- logcat_panic(HELP_FALSE, "failed to clear the '%s' log\n", clearFail);
+ logcat_panic(context, HELP_FALSE,
+ "failed to clear the '%s' log\n", clearFail);
+ goto close;
}
if (setSizeFail) {
- logcat_panic(HELP_FALSE, "failed to set the '%s' log size\n",
- setSizeFail);
+ logcat_panic(context, HELP_FALSE,
+ "failed to set the '%s' log size\n", setSizeFail);
+ goto close;
}
if (getSizeFail) {
- logcat_panic(HELP_FALSE, "failed to get the readable '%s' log size",
- getSizeFail);
+ logcat_panic(context, HELP_FALSE,
+ "failed to get the readable '%s' log size", getSizeFail);
+ goto close;
}
if (setPruneList) {
@@ -1194,13 +1444,15 @@
if (asprintf(&buf, "%-*s", (int)(bLen - 1), setPruneList) > 0) {
buf[len] = '\0';
if (android_logger_set_prune_list(logger_list, buf, bLen)) {
- logcat_panic(HELP_FALSE, "failed to set the prune list");
+ logcat_panic(context, HELP_FALSE,
+ "failed to set the prune list");
}
free(buf);
} else {
- logcat_panic(HELP_FALSE, "failed to set the prune list (alloc)");
+ logcat_panic(context, HELP_FALSE,
+ "failed to set the prune list (alloc)");
}
- return EXIT_SUCCESS;
+ goto close;
}
if (printStatistics || getPruneList) {
@@ -1229,7 +1481,8 @@
}
if (!buf) {
- logcat_panic(HELP_FALSE, "failed to read data");
+ logcat_panic(context, HELP_FALSE, "failed to read data");
+ goto close;
}
// remove trailing FF
@@ -1251,31 +1504,32 @@
}
}
- printf("%s", cp);
+ len = strlen(cp);
+ TEMP_FAILURE_RETRY(write(context->output_fd, cp, len));
delete[] buf;
- return EXIT_SUCCESS;
+ goto close;
}
if (getLogSize || setLogSize || clearLog) {
- return EXIT_SUCCESS;
+ goto close;
}
- setupOutputAndSchedulingPolicy((mode & ANDROID_LOG_NONBLOCK) == 0);
+ setupOutputAndSchedulingPolicy(context, (mode & ANDROID_LOG_NONBLOCK) == 0);
+ if (context->stop) goto close;
// LOG_EVENT_INT(10, 12345);
// LOG_EVENT_LONG(11, 0x1122334455667788LL);
// LOG_EVENT_STRING(0, "whassup, doc?");
dev = NULL;
- log_device_t unexpected("unexpected", false);
- while (!g_maxCount || (g_printCount < g_maxCount)) {
+ while (!context->stop &&
+ (!context->maxCount || (context->printCount < context->maxCount))) {
struct log_msg log_msg;
- log_device_t* d;
int ret = android_logger_list_read(logger_list, &log_msg);
-
if (ret == 0) {
- logcat_panic(HELP_FALSE, "read: unexpected EOF!\n");
+ logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
+ break;
}
if (ret < 0) {
@@ -1284,37 +1538,81 @@
}
if (ret == -EIO) {
- logcat_panic(HELP_FALSE, "read: unexpected EOF!\n");
+ logcat_panic(context, HELP_FALSE, "read: unexpected EOF!\n");
+ break;
}
if (ret == -EINVAL) {
- logcat_panic(HELP_FALSE, "read: unexpected length.\n");
+ logcat_panic(context, HELP_FALSE, "read: unexpected length.\n");
+ break;
}
- logcat_panic(HELP_FALSE, "logcat read failure");
+ logcat_panic(context, HELP_FALSE, "logcat read failure");
+ break;
}
+ log_device_t* d;
for (d = devices; d; d = d->next) {
if (android_name_to_log_id(d->device) == log_msg.id()) {
break;
}
}
if (!d) {
- g_devCount = 2; // set to Multiple
+ context->devCount = 2; // set to Multiple
d = &unexpected;
d->binary = log_msg.id() == LOG_ID_EVENTS;
}
if (dev != d) {
dev = d;
- maybePrintStart(dev, printDividers);
+ maybePrintStart(context, dev, printDividers);
+ if (context->stop) break;
}
- if (g_printBinary) {
- printBinary(&log_msg);
+ if (context->printBinary) {
+ printBinary(context, &log_msg);
} else {
- processBuffer(dev, &log_msg);
+ processBuffer(context, dev, &log_msg);
}
}
+close:
android_logger_list_free(logger_list);
- return EXIT_SUCCESS;
+exit:
+ return context->retval;
+}
+
+// Can block
+int android_logcat_run_command(android_logcat_context ctx,
+ int output, int error,
+ int argc, char* const* argv,
+ char* const* envp) {
+ android_logcat_context_internal* context = ctx;
+
+ context->output_fd = output;
+ context->error_fd = error;
+ context->argc = argc;
+ context->argv = argv;
+ context->envp = envp;
+ context->stop = false;
+ return __logcat(context);
+}
+
+// Finished with context
+int android_logcat_destroy(android_logcat_context* ctx) {
+ android_logcat_context_internal* context = *ctx;
+
+ *ctx = NULL;
+
+ context->stop = true;
+
+ delete context->regex;
+ android::close_output(context);
+ android::close_error(context);
+
+ android_closeEventTagMap(context->eventTagMap);
+
+ int retval = context->retval;
+
+ free(context);
+
+ return retval;
}
diff --git a/logcat/logcat_main.cpp b/logcat/logcat_main.cpp
new file mode 100644
index 0000000..9477e79
--- /dev/null
+++ b/logcat/logcat_main.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+
+#include <log/logcat.h>
+
+int main(int argc, char** argv, char** envp) {
+ android_logcat_context ctx = create_android_logcat();
+ if (!ctx) return -1;
+ signal(SIGPIPE, exit);
+ int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp);
+ int ret = android_logcat_destroy(&ctx);
+ if (!ret) ret = retval;
+ return ret;
+}
diff --git a/logcat/tests/logcat_test.cpp b/logcat/tests/logcat_test.cpp
index a12524c..8f2da21 100644
--- a/logcat/tests/logcat_test.cpp
+++ b/logcat/tests/logcat_test.cpp
@@ -278,7 +278,7 @@
char buffer[BIG_BUFFER];
snprintf(buffer, sizeof(buffer),
- "logcat -v long -b all -t %d 2>/dev/null", num);
+ "ANDROID_PRINTF_LOG=long logcat -b all -t %d 2>/dev/null", num);
FILE* fp;
ASSERT_TRUE(NULL != (fp = popen(buffer, "r")));