| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 1 | // Copyright 2006 The Android Open Source Project | 
|  | 2 |  | 
|  | 3 | #include <cutils/logger.h> | 
|  | 4 | #include <cutils/logd.h> | 
|  | 5 | #include <cutils/sockets.h> | 
|  | 6 | #include <cutils/logprint.h> | 
|  | 7 | #include <cutils/event_tag_map.h> | 
|  | 8 |  | 
|  | 9 | #include <stdio.h> | 
|  | 10 | #include <stdlib.h> | 
|  | 11 | #include <stdarg.h> | 
|  | 12 | #include <string.h> | 
|  | 13 | #include <unistd.h> | 
|  | 14 | #include <fcntl.h> | 
|  | 15 | #include <time.h> | 
|  | 16 | #include <errno.h> | 
|  | 17 | #include <assert.h> | 
|  | 18 | #include <ctype.h> | 
|  | 19 | #include <sys/socket.h> | 
|  | 20 | #include <sys/stat.h> | 
|  | 21 | #include <arpa/inet.h> | 
|  | 22 |  | 
|  | 23 | #define DEFAULT_LOG_ROTATE_SIZE_KBYTES 16 | 
|  | 24 | #define DEFAULT_MAX_ROTATED_LOGS 4 | 
|  | 25 |  | 
|  | 26 | static AndroidLogFormat * g_logformat; | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 27 | static bool g_nonblock = false; | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 28 | static int g_tail_lines = 0; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 29 |  | 
|  | 30 | /* logd prefixes records with a length field */ | 
|  | 31 | #define RECORD_LENGTH_FIELD_SIZE_BYTES sizeof(uint32_t) | 
|  | 32 |  | 
|  | 33 | #define LOG_FILE_DIR    "/dev/log/" | 
|  | 34 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 35 | struct queued_entry_t { | 
|  | 36 | union { | 
|  | 37 | unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4))); | 
|  | 38 | struct logger_entry entry __attribute__((aligned(4))); | 
|  | 39 | }; | 
|  | 40 | queued_entry_t* next; | 
|  | 41 |  | 
|  | 42 | queued_entry_t() { | 
|  | 43 | next = NULL; | 
|  | 44 | } | 
|  | 45 | }; | 
|  | 46 |  | 
|  | 47 | static int cmp(queued_entry_t* a, queued_entry_t* b) { | 
|  | 48 | int n = a->entry.sec - b->entry.sec; | 
|  | 49 | if (n != 0) { | 
|  | 50 | return n; | 
|  | 51 | } | 
|  | 52 | return a->entry.nsec - b->entry.nsec; | 
|  | 53 | } | 
|  | 54 |  | 
|  | 55 | struct log_device_t { | 
|  | 56 | char* device; | 
|  | 57 | bool binary; | 
|  | 58 | int fd; | 
|  | 59 | bool printed; | 
|  | 60 | char label; | 
|  | 61 |  | 
|  | 62 | queued_entry_t* queue; | 
|  | 63 | log_device_t* next; | 
|  | 64 |  | 
|  | 65 | log_device_t(char* d, bool b, char l) { | 
|  | 66 | device = d; | 
|  | 67 | binary = b; | 
|  | 68 | label = l; | 
| Mathias Agopian | 5084452 | 2010-03-17 16:10:26 -0700 | [diff] [blame] | 69 | queue = NULL; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 70 | next = NULL; | 
|  | 71 | printed = false; | 
|  | 72 | } | 
|  | 73 |  | 
|  | 74 | void enqueue(queued_entry_t* entry) { | 
|  | 75 | if (this->queue == NULL) { | 
|  | 76 | this->queue = entry; | 
|  | 77 | } else { | 
|  | 78 | queued_entry_t** e = &this->queue; | 
|  | 79 | while (*e && cmp(entry, *e) >= 0) { | 
|  | 80 | e = &((*e)->next); | 
|  | 81 | } | 
|  | 82 | entry->next = *e; | 
|  | 83 | *e = entry; | 
|  | 84 | } | 
|  | 85 | } | 
|  | 86 | }; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 87 |  | 
|  | 88 | namespace android { | 
|  | 89 |  | 
|  | 90 | /* Global Variables */ | 
|  | 91 |  | 
|  | 92 | static const char * g_outputFileName = NULL; | 
|  | 93 | static int g_logRotateSizeKBytes = 0;                   // 0 means "no log rotation" | 
|  | 94 | static int g_maxRotatedLogs = DEFAULT_MAX_ROTATED_LOGS; // 0 means "unbounded" | 
|  | 95 | static int g_outFD = -1; | 
|  | 96 | static off_t g_outByteCount = 0; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 97 | static int g_printBinary = 0; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 98 | static int g_devCount = 0; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 99 |  | 
|  | 100 | static EventTagMap* g_eventTagMap = NULL; | 
|  | 101 |  | 
|  | 102 | static int openLogFile (const char *pathname) | 
|  | 103 | { | 
|  | 104 | return open(g_outputFileName, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); | 
|  | 105 | } | 
|  | 106 |  | 
|  | 107 | static void rotateLogs() | 
|  | 108 | { | 
|  | 109 | int err; | 
|  | 110 |  | 
|  | 111 | // Can't rotate logs if we're not outputting to a file | 
|  | 112 | if (g_outputFileName == NULL) { | 
|  | 113 | return; | 
|  | 114 | } | 
|  | 115 |  | 
|  | 116 | close(g_outFD); | 
|  | 117 |  | 
|  | 118 | for (int i = g_maxRotatedLogs ; i > 0 ; i--) { | 
|  | 119 | char *file0, *file1; | 
|  | 120 |  | 
|  | 121 | asprintf(&file1, "%s.%d", g_outputFileName, i); | 
|  | 122 |  | 
|  | 123 | if (i - 1 == 0) { | 
|  | 124 | asprintf(&file0, "%s", g_outputFileName); | 
|  | 125 | } else { | 
|  | 126 | asprintf(&file0, "%s.%d", g_outputFileName, i - 1); | 
|  | 127 | } | 
|  | 128 |  | 
|  | 129 | err = rename (file0, file1); | 
|  | 130 |  | 
|  | 131 | if (err < 0 && errno != ENOENT) { | 
|  | 132 | perror("while rotating log files"); | 
|  | 133 | } | 
|  | 134 |  | 
|  | 135 | free(file1); | 
|  | 136 | free(file0); | 
|  | 137 | } | 
|  | 138 |  | 
|  | 139 | g_outFD = openLogFile (g_outputFileName); | 
|  | 140 |  | 
|  | 141 | if (g_outFD < 0) { | 
|  | 142 | perror ("couldn't open output file"); | 
|  | 143 | exit(-1); | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | g_outByteCount = 0; | 
|  | 147 |  | 
|  | 148 | } | 
|  | 149 |  | 
|  | 150 | void printBinary(struct logger_entry *buf) | 
|  | 151 | { | 
|  | 152 | size_t size = sizeof(logger_entry) + buf->len; | 
|  | 153 | int ret; | 
|  | 154 |  | 
|  | 155 | do { | 
|  | 156 | ret = write(g_outFD, buf, size); | 
|  | 157 | } while (ret < 0 && errno == EINTR); | 
|  | 158 | } | 
|  | 159 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 160 | static void processBuffer(log_device_t* dev, struct logger_entry *buf) | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 161 | { | 
| Mathias Agopian | 5084452 | 2010-03-17 16:10:26 -0700 | [diff] [blame] | 162 | int bytesWritten = 0; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 163 | int err; | 
|  | 164 | AndroidLogEntry entry; | 
|  | 165 | char binaryMsgBuf[1024]; | 
|  | 166 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 167 | if (dev->binary) { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 168 | err = android_log_processBinaryLogBuffer(buf, &entry, g_eventTagMap, | 
|  | 169 | binaryMsgBuf, sizeof(binaryMsgBuf)); | 
|  | 170 | //printf(">>> pri=%d len=%d msg='%s'\n", | 
|  | 171 | //    entry.priority, entry.messageLen, entry.message); | 
|  | 172 | } else { | 
|  | 173 | err = android_log_processLogBuffer(buf, &entry); | 
|  | 174 | } | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 175 | if (err < 0) { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 176 | goto error; | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 177 | } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 178 |  | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 179 | if (android_log_shouldPrintLine(g_logformat, entry.tag, entry.priority)) { | 
|  | 180 | if (false && g_devCount > 1) { | 
|  | 181 | binaryMsgBuf[0] = dev->label; | 
|  | 182 | binaryMsgBuf[1] = ' '; | 
|  | 183 | bytesWritten = write(g_outFD, binaryMsgBuf, 2); | 
|  | 184 | if (bytesWritten < 0) { | 
|  | 185 | perror("output error"); | 
|  | 186 | exit(-1); | 
|  | 187 | } | 
|  | 188 | } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 189 |  | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 190 | bytesWritten = android_log_printLogLine(g_logformat, g_outFD, &entry); | 
|  | 191 |  | 
|  | 192 | if (bytesWritten < 0) { | 
|  | 193 | perror("output error"); | 
|  | 194 | exit(-1); | 
|  | 195 | } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 196 | } | 
|  | 197 |  | 
|  | 198 | g_outByteCount += bytesWritten; | 
|  | 199 |  | 
|  | 200 | if (g_logRotateSizeKBytes > 0 | 
|  | 201 | && (g_outByteCount / 1024) >= g_logRotateSizeKBytes | 
|  | 202 | ) { | 
|  | 203 | rotateLogs(); | 
|  | 204 | } | 
|  | 205 |  | 
|  | 206 | error: | 
|  | 207 | //fprintf (stderr, "Error processing record\n"); | 
|  | 208 | return; | 
|  | 209 | } | 
|  | 210 |  | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 211 | static void chooseFirst(log_device_t* dev, log_device_t** firstdev) { | 
|  | 212 | for (*firstdev = NULL; dev != NULL; dev = dev->next) { | 
|  | 213 | if (dev->queue != NULL && (*firstdev == NULL || cmp(dev->queue, (*firstdev)->queue) < 0)) { | 
|  | 214 | *firstdev = dev; | 
|  | 215 | } | 
|  | 216 | } | 
|  | 217 | } | 
|  | 218 |  | 
|  | 219 | static void maybePrintStart(log_device_t* dev) { | 
|  | 220 | if (!dev->printed) { | 
|  | 221 | dev->printed = true; | 
|  | 222 | if (g_devCount > 1 && !g_printBinary) { | 
|  | 223 | char buf[1024]; | 
|  | 224 | snprintf(buf, sizeof(buf), "--------- beginning of %s\n", dev->device); | 
|  | 225 | if (write(g_outFD, buf, strlen(buf)) < 0) { | 
|  | 226 | perror("output error"); | 
|  | 227 | exit(-1); | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 228 | } | 
|  | 229 | } | 
|  | 230 | } | 
|  | 231 | } | 
|  | 232 |  | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 233 | static void skipNextEntry(log_device_t* dev) { | 
|  | 234 | maybePrintStart(dev); | 
|  | 235 | queued_entry_t* entry = dev->queue; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 236 | dev->queue = entry->next; | 
|  | 237 | delete entry; | 
|  | 238 | } | 
|  | 239 |  | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 240 | static void printNextEntry(log_device_t* dev) { | 
|  | 241 | maybePrintStart(dev); | 
|  | 242 | if (g_printBinary) { | 
|  | 243 | printBinary(&dev->queue->entry); | 
|  | 244 | } else { | 
|  | 245 | processBuffer(dev, &dev->queue->entry); | 
|  | 246 | } | 
|  | 247 | skipNextEntry(dev); | 
|  | 248 | } | 
|  | 249 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 250 | static void readLogLines(log_device_t* devices) | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 251 | { | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 252 | log_device_t* dev; | 
|  | 253 | int max = 0; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 254 | int ret; | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 255 | int queued_lines = 0; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 256 | bool sleep = true; | 
|  | 257 |  | 
|  | 258 | int result; | 
|  | 259 | fd_set readset; | 
|  | 260 |  | 
|  | 261 | for (dev=devices; dev; dev = dev->next) { | 
|  | 262 | if (dev->fd > max) { | 
|  | 263 | max = dev->fd; | 
|  | 264 | } | 
|  | 265 | } | 
|  | 266 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 267 | while (1) { | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 268 | do { | 
|  | 269 | timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR. | 
|  | 270 | FD_ZERO(&readset); | 
|  | 271 | for (dev=devices; dev; dev = dev->next) { | 
|  | 272 | FD_SET(dev->fd, &readset); | 
|  | 273 | } | 
|  | 274 | result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout); | 
|  | 275 | } while (result == -1 && errno == EINTR); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 276 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 277 | if (result >= 0) { | 
|  | 278 | for (dev=devices; dev; dev = dev->next) { | 
|  | 279 | if (FD_ISSET(dev->fd, &readset)) { | 
| Mathias Agopian | 5084452 | 2010-03-17 16:10:26 -0700 | [diff] [blame] | 280 | queued_entry_t* entry = new queued_entry_t(); | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 281 | /* NOTE: driver guarantees we read exactly one full entry */ | 
|  | 282 | ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN); | 
|  | 283 | if (ret < 0) { | 
|  | 284 | if (errno == EINTR) { | 
|  | 285 | delete entry; | 
|  | 286 | goto next; | 
|  | 287 | } | 
|  | 288 | if (errno == EAGAIN) { | 
|  | 289 | delete entry; | 
|  | 290 | break; | 
|  | 291 | } | 
|  | 292 | perror("logcat read"); | 
|  | 293 | exit(EXIT_FAILURE); | 
|  | 294 | } | 
|  | 295 | else if (!ret) { | 
|  | 296 | fprintf(stderr, "read: Unexpected EOF!\n"); | 
|  | 297 | exit(EXIT_FAILURE); | 
|  | 298 | } | 
|  | 299 |  | 
|  | 300 | entry->entry.msg[entry->entry.len] = '\0'; | 
|  | 301 |  | 
|  | 302 | dev->enqueue(entry); | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 303 | ++queued_lines; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 304 | } | 
|  | 305 | } | 
|  | 306 |  | 
|  | 307 | if (result == 0) { | 
|  | 308 | // we did our short timeout trick and there's nothing new | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 309 | // print everything we have and wait for more data | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 310 | sleep = true; | 
|  | 311 | while (true) { | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 312 | chooseFirst(devices, &dev); | 
|  | 313 | if (dev == NULL) { | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 314 | break; | 
|  | 315 | } | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 316 | if (g_tail_lines == 0 || queued_lines <= g_tail_lines) { | 
|  | 317 | printNextEntry(dev); | 
|  | 318 | } else { | 
|  | 319 | skipNextEntry(dev); | 
|  | 320 | } | 
|  | 321 | --queued_lines; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 322 | } | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 323 |  | 
|  | 324 | // the caller requested to just dump the log and exit | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 325 | if (g_nonblock) { | 
|  | 326 | exit(0); | 
|  | 327 | } | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 328 | } else { | 
|  | 329 | // print all that aren't the last in their list | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 330 | sleep = false; | 
|  | 331 | while (g_tail_lines == 0 || queued_lines > g_tail_lines) { | 
|  | 332 | chooseFirst(devices, &dev); | 
|  | 333 | if (dev == NULL || dev->queue->next == NULL) { | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 334 | break; | 
|  | 335 | } | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 336 | if (g_tail_lines == 0) { | 
|  | 337 | printNextEntry(dev); | 
|  | 338 | } else { | 
|  | 339 | skipNextEntry(dev); | 
|  | 340 | } | 
|  | 341 | --queued_lines; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 342 | } | 
|  | 343 | } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 344 | } | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 345 | next: | 
|  | 346 | ; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 347 | } | 
|  | 348 | } | 
|  | 349 |  | 
|  | 350 | static int clearLog(int logfd) | 
|  | 351 | { | 
|  | 352 | return ioctl(logfd, LOGGER_FLUSH_LOG); | 
|  | 353 | } | 
|  | 354 |  | 
|  | 355 | /* returns the total size of the log's ring buffer */ | 
|  | 356 | static int getLogSize(int logfd) | 
|  | 357 | { | 
|  | 358 | return ioctl(logfd, LOGGER_GET_LOG_BUF_SIZE); | 
|  | 359 | } | 
|  | 360 |  | 
|  | 361 | /* returns the readable size of the log's ring buffer (that is, amount of the log consumed) */ | 
|  | 362 | static int getLogReadableSize(int logfd) | 
|  | 363 | { | 
|  | 364 | return ioctl(logfd, LOGGER_GET_LOG_LEN); | 
|  | 365 | } | 
|  | 366 |  | 
|  | 367 | static void setupOutput() | 
|  | 368 | { | 
|  | 369 |  | 
|  | 370 | if (g_outputFileName == NULL) { | 
|  | 371 | g_outFD = STDOUT_FILENO; | 
|  | 372 |  | 
|  | 373 | } else { | 
|  | 374 | struct stat statbuf; | 
|  | 375 |  | 
|  | 376 | g_outFD = openLogFile (g_outputFileName); | 
|  | 377 |  | 
|  | 378 | if (g_outFD < 0) { | 
|  | 379 | perror ("couldn't open output file"); | 
|  | 380 | exit(-1); | 
|  | 381 | } | 
|  | 382 |  | 
|  | 383 | fstat(g_outFD, &statbuf); | 
|  | 384 |  | 
|  | 385 | g_outByteCount = statbuf.st_size; | 
|  | 386 | } | 
|  | 387 | } | 
|  | 388 |  | 
|  | 389 | static void show_help(const char *cmd) | 
|  | 390 | { | 
|  | 391 | fprintf(stderr,"Usage: %s [options] [filterspecs]\n", cmd); | 
|  | 392 |  | 
|  | 393 | fprintf(stderr, "options include:\n" | 
|  | 394 | "  -s              Set default filter to silent.\n" | 
|  | 395 | "                  Like specifying filterspec '*:s'\n" | 
|  | 396 | "  -f <filename>   Log to file. Default to stdout\n" | 
|  | 397 | "  -r [<kbytes>]   Rotate log every kbytes. (16 if unspecified). Requires -f\n" | 
|  | 398 | "  -n <count>      Sets max number of rotated logs to <count>, default 4\n" | 
|  | 399 | "  -v <format>     Sets the log print format, where <format> is one of:\n\n" | 
|  | 400 | "                  brief process tag thread raw time threadtime long\n\n" | 
|  | 401 | "  -c              clear (flush) the entire log and exit\n" | 
|  | 402 | "  -d              dump the log and then exit (don't block)\n" | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 403 | "  -t <count>      print only the most recent <count> lines (implies -d)\n" | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 404 | "  -g              get the size of the log's ring buffer and exit\n" | 
| Wink Saville | ba9608f | 2010-06-18 10:15:08 -0700 | [diff] [blame] | 405 | "  -b <buffer>     Request alternate ring buffer, 'main', 'system', 'radio'\n" | 
|  | 406 | "                  or 'events'. Multiple -b parameters are allowed and the\n" | 
|  | 407 | "                  results are interleaved. The default is -b main -b system.\n" | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 408 | "  -B              output the log in binary"); | 
|  | 409 |  | 
|  | 410 |  | 
|  | 411 | fprintf(stderr,"\nfilterspecs are a series of \n" | 
|  | 412 | "  <tag>[:priority]\n\n" | 
|  | 413 | "where <tag> is a log component tag (or * for all) and priority is:\n" | 
|  | 414 | "  V    Verbose\n" | 
|  | 415 | "  D    Debug\n" | 
|  | 416 | "  I    Info\n" | 
|  | 417 | "  W    Warn\n" | 
|  | 418 | "  E    Error\n" | 
|  | 419 | "  F    Fatal\n" | 
|  | 420 | "  S    Silent (supress all output)\n" | 
|  | 421 | "\n'*' means '*:d' and <tag> by itself means <tag>:v\n" | 
|  | 422 | "\nIf not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS.\n" | 
|  | 423 | "If no filterspec is found, filter defaults to '*:I'\n" | 
|  | 424 | "\nIf not specified with -v, format is set from ANDROID_PRINTF_LOG\n" | 
|  | 425 | "or defaults to \"brief\"\n\n"); | 
|  | 426 |  | 
|  | 427 |  | 
|  | 428 |  | 
|  | 429 | } | 
|  | 430 |  | 
|  | 431 |  | 
|  | 432 | } /* namespace android */ | 
|  | 433 |  | 
|  | 434 | static int setLogFormat(const char * formatString) | 
|  | 435 | { | 
|  | 436 | static AndroidLogPrintFormat format; | 
|  | 437 |  | 
|  | 438 | format = android_log_formatFromString(formatString); | 
|  | 439 |  | 
|  | 440 | if (format == FORMAT_OFF) { | 
|  | 441 | // FORMAT_OFF means invalid string | 
|  | 442 | return -1; | 
|  | 443 | } | 
|  | 444 |  | 
|  | 445 | android_log_setPrintFormat(g_logformat, format); | 
|  | 446 |  | 
|  | 447 | return 0; | 
|  | 448 | } | 
|  | 449 |  | 
|  | 450 | extern "C" void logprint_run_tests(void); | 
|  | 451 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 452 | int main(int argc, char **argv) | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 453 | { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 454 | int err; | 
|  | 455 | int hasSetLogFormat = 0; | 
|  | 456 | int clearLog = 0; | 
|  | 457 | int getLogSize = 0; | 
|  | 458 | int mode = O_RDONLY; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 459 | const char *forceFilters = NULL; | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 460 | log_device_t* devices = NULL; | 
|  | 461 | log_device_t* dev; | 
|  | 462 | bool needBinary = false; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 463 |  | 
|  | 464 | g_logformat = android_log_format_new(); | 
|  | 465 |  | 
|  | 466 | if (argc == 2 && 0 == strcmp(argv[1], "--test")) { | 
|  | 467 | logprint_run_tests(); | 
|  | 468 | exit(0); | 
|  | 469 | } | 
|  | 470 |  | 
|  | 471 | if (argc == 2 && 0 == strcmp(argv[1], "--help")) { | 
|  | 472 | android::show_help(argv[0]); | 
|  | 473 | exit(0); | 
|  | 474 | } | 
|  | 475 |  | 
|  | 476 | for (;;) { | 
|  | 477 | int ret; | 
|  | 478 |  | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 479 | ret = getopt(argc, argv, "cdt:gsQf:r::n:v:b:B"); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 480 |  | 
|  | 481 | if (ret < 0) { | 
|  | 482 | break; | 
|  | 483 | } | 
|  | 484 |  | 
|  | 485 | switch(ret) { | 
|  | 486 | case 's': | 
|  | 487 | // default to all silent | 
|  | 488 | android_log_addFilterRule(g_logformat, "*:s"); | 
|  | 489 | break; | 
|  | 490 |  | 
|  | 491 | case 'c': | 
|  | 492 | clearLog = 1; | 
|  | 493 | mode = O_WRONLY; | 
|  | 494 | break; | 
|  | 495 |  | 
|  | 496 | case 'd': | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 497 | g_nonblock = true; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 498 | break; | 
|  | 499 |  | 
| Dan Egnor | d1d3b6d | 2010-03-11 20:32:17 -0800 | [diff] [blame] | 500 | case 't': | 
|  | 501 | g_nonblock = true; | 
|  | 502 | g_tail_lines = atoi(optarg); | 
|  | 503 | break; | 
|  | 504 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 505 | case 'g': | 
|  | 506 | getLogSize = 1; | 
|  | 507 | break; | 
|  | 508 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 509 | case 'b': { | 
|  | 510 | char* buf = (char*) malloc(strlen(LOG_FILE_DIR) + strlen(optarg) + 1); | 
|  | 511 | strcpy(buf, LOG_FILE_DIR); | 
|  | 512 | strcat(buf, optarg); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 513 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 514 | bool binary = strcmp(optarg, "events") == 0; | 
|  | 515 | if (binary) { | 
|  | 516 | needBinary = true; | 
|  | 517 | } | 
|  | 518 |  | 
|  | 519 | if (devices) { | 
|  | 520 | dev = devices; | 
|  | 521 | while (dev->next) { | 
|  | 522 | dev = dev->next; | 
|  | 523 | } | 
|  | 524 | dev->next = new log_device_t(buf, binary, optarg[0]); | 
|  | 525 | } else { | 
|  | 526 | devices = new log_device_t(buf, binary, optarg[0]); | 
|  | 527 | } | 
|  | 528 | android::g_devCount++; | 
|  | 529 | } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 530 | break; | 
|  | 531 |  | 
|  | 532 | case 'B': | 
|  | 533 | android::g_printBinary = 1; | 
|  | 534 | break; | 
|  | 535 |  | 
|  | 536 | case 'f': | 
|  | 537 | // redirect output to a file | 
|  | 538 |  | 
|  | 539 | android::g_outputFileName = optarg; | 
|  | 540 |  | 
|  | 541 | break; | 
|  | 542 |  | 
|  | 543 | case 'r': | 
|  | 544 | if (optarg == NULL) { | 
|  | 545 | android::g_logRotateSizeKBytes | 
|  | 546 | = DEFAULT_LOG_ROTATE_SIZE_KBYTES; | 
|  | 547 | } else { | 
|  | 548 | long logRotateSize; | 
|  | 549 | char *lastDigit; | 
|  | 550 |  | 
|  | 551 | if (!isdigit(optarg[0])) { | 
|  | 552 | fprintf(stderr,"Invalid parameter to -r\n"); | 
|  | 553 | android::show_help(argv[0]); | 
|  | 554 | exit(-1); | 
|  | 555 | } | 
|  | 556 | android::g_logRotateSizeKBytes = atoi(optarg); | 
|  | 557 | } | 
|  | 558 | break; | 
|  | 559 |  | 
|  | 560 | case 'n': | 
|  | 561 | if (!isdigit(optarg[0])) { | 
|  | 562 | fprintf(stderr,"Invalid parameter to -r\n"); | 
|  | 563 | android::show_help(argv[0]); | 
|  | 564 | exit(-1); | 
|  | 565 | } | 
|  | 566 |  | 
|  | 567 | android::g_maxRotatedLogs = atoi(optarg); | 
|  | 568 | break; | 
|  | 569 |  | 
|  | 570 | case 'v': | 
|  | 571 | err = setLogFormat (optarg); | 
|  | 572 | if (err < 0) { | 
|  | 573 | fprintf(stderr,"Invalid parameter to -v\n"); | 
|  | 574 | android::show_help(argv[0]); | 
|  | 575 | exit(-1); | 
|  | 576 | } | 
|  | 577 |  | 
|  | 578 | hasSetLogFormat = 1; | 
|  | 579 | break; | 
|  | 580 |  | 
|  | 581 | case 'Q': | 
|  | 582 | /* this is a *hidden* option used to start a version of logcat                 */ | 
|  | 583 | /* in an emulated device only. it basically looks for androidboot.logcat=      */ | 
|  | 584 | /* on the kernel command line. If something is found, it extracts a log filter */ | 
|  | 585 | /* and uses it to run the program. If nothing is found, the program should     */ | 
|  | 586 | /* quit immediately                                                            */ | 
|  | 587 | #define  KERNEL_OPTION  "androidboot.logcat=" | 
|  | 588 | #define  CONSOLE_OPTION "androidboot.console=" | 
|  | 589 | { | 
|  | 590 | int          fd; | 
|  | 591 | char*        logcat; | 
|  | 592 | char*        console; | 
|  | 593 | int          force_exit = 1; | 
|  | 594 | static char  cmdline[1024]; | 
|  | 595 |  | 
|  | 596 | fd = open("/proc/cmdline", O_RDONLY); | 
|  | 597 | if (fd >= 0) { | 
|  | 598 | int  n = read(fd, cmdline, sizeof(cmdline)-1 ); | 
|  | 599 | if (n < 0) n = 0; | 
|  | 600 | cmdline[n] = 0; | 
|  | 601 | close(fd); | 
|  | 602 | } else { | 
|  | 603 | cmdline[0] = 0; | 
|  | 604 | } | 
|  | 605 |  | 
|  | 606 | logcat  = strstr( cmdline, KERNEL_OPTION ); | 
|  | 607 | console = strstr( cmdline, CONSOLE_OPTION ); | 
|  | 608 | if (logcat != NULL) { | 
|  | 609 | char*  p = logcat + sizeof(KERNEL_OPTION)-1;; | 
|  | 610 | char*  q = strpbrk( p, " \t\n\r" );; | 
|  | 611 |  | 
|  | 612 | if (q != NULL) | 
|  | 613 | *q = 0; | 
|  | 614 |  | 
|  | 615 | forceFilters = p; | 
|  | 616 | force_exit   = 0; | 
|  | 617 | } | 
|  | 618 | /* if nothing found or invalid filters, exit quietly */ | 
|  | 619 | if (force_exit) | 
|  | 620 | exit(0); | 
|  | 621 |  | 
|  | 622 | /* redirect our output to the emulator console */ | 
|  | 623 | if (console) { | 
|  | 624 | char*  p = console + sizeof(CONSOLE_OPTION)-1; | 
|  | 625 | char*  q = strpbrk( p, " \t\n\r" ); | 
|  | 626 | char   devname[64]; | 
|  | 627 | int    len; | 
|  | 628 |  | 
|  | 629 | if (q != NULL) { | 
|  | 630 | len = q - p; | 
|  | 631 | } else | 
|  | 632 | len = strlen(p); | 
|  | 633 |  | 
|  | 634 | len = snprintf( devname, sizeof(devname), "/dev/%.*s", len, p ); | 
|  | 635 | fprintf(stderr, "logcat using %s (%d)\n", devname, len); | 
|  | 636 | if (len < (int)sizeof(devname)) { | 
|  | 637 | fd = open( devname, O_WRONLY ); | 
|  | 638 | if (fd >= 0) { | 
|  | 639 | dup2(fd, 1); | 
|  | 640 | dup2(fd, 2); | 
|  | 641 | close(fd); | 
|  | 642 | } | 
|  | 643 | } | 
|  | 644 | } | 
|  | 645 | } | 
|  | 646 | break; | 
|  | 647 |  | 
|  | 648 | default: | 
|  | 649 | fprintf(stderr,"Unrecognized Option\n"); | 
|  | 650 | android::show_help(argv[0]); | 
|  | 651 | exit(-1); | 
|  | 652 | break; | 
|  | 653 | } | 
|  | 654 | } | 
|  | 655 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 656 | if (!devices) { | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 657 | devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm'); | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 658 | android::g_devCount = 1; | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 659 | int accessmode = | 
|  | 660 | (mode & O_RDONLY) ? R_OK : 0 | 
|  | 661 | | (mode & O_WRONLY) ? W_OK : 0; | 
|  | 662 | // only add this if it's available | 
|  | 663 | if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) { | 
|  | 664 | devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's'); | 
|  | 665 | android::g_devCount++; | 
|  | 666 | } | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 667 | } | 
|  | 668 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 669 | if (android::g_logRotateSizeKBytes != 0 | 
|  | 670 | && android::g_outputFileName == NULL | 
|  | 671 | ) { | 
|  | 672 | fprintf(stderr,"-r requires -f as well\n"); | 
|  | 673 | android::show_help(argv[0]); | 
|  | 674 | exit(-1); | 
|  | 675 | } | 
|  | 676 |  | 
|  | 677 | android::setupOutput(); | 
|  | 678 |  | 
|  | 679 | if (hasSetLogFormat == 0) { | 
|  | 680 | const char* logFormat = getenv("ANDROID_PRINTF_LOG"); | 
|  | 681 |  | 
|  | 682 | if (logFormat != NULL) { | 
|  | 683 | err = setLogFormat(logFormat); | 
|  | 684 |  | 
|  | 685 | if (err < 0) { | 
|  | 686 | fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n", | 
|  | 687 | logFormat); | 
|  | 688 | } | 
|  | 689 | } | 
|  | 690 | } | 
|  | 691 |  | 
|  | 692 | if (forceFilters) { | 
|  | 693 | err = android_log_addFilterString(g_logformat, forceFilters); | 
|  | 694 | if (err < 0) { | 
|  | 695 | fprintf (stderr, "Invalid filter expression in -logcat option\n"); | 
|  | 696 | exit(0); | 
|  | 697 | } | 
|  | 698 | } else if (argc == optind) { | 
|  | 699 | // Add from environment variable | 
|  | 700 | char *env_tags_orig = getenv("ANDROID_LOG_TAGS"); | 
|  | 701 |  | 
|  | 702 | if (env_tags_orig != NULL) { | 
|  | 703 | err = android_log_addFilterString(g_logformat, env_tags_orig); | 
|  | 704 |  | 
|  | 705 | if (err < 0) { | 
|  | 706 | fprintf(stderr, "Invalid filter expression in" | 
|  | 707 | " ANDROID_LOG_TAGS\n"); | 
|  | 708 | android::show_help(argv[0]); | 
|  | 709 | exit(-1); | 
|  | 710 | } | 
|  | 711 | } | 
|  | 712 | } else { | 
|  | 713 | // Add from commandline | 
|  | 714 | for (int i = optind ; i < argc ; i++) { | 
|  | 715 | err = android_log_addFilterString(g_logformat, argv[i]); | 
|  | 716 |  | 
|  | 717 | if (err < 0) { | 
|  | 718 | fprintf (stderr, "Invalid filter expression '%s'\n", argv[i]); | 
|  | 719 | android::show_help(argv[0]); | 
|  | 720 | exit(-1); | 
|  | 721 | } | 
|  | 722 | } | 
|  | 723 | } | 
|  | 724 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 725 | dev = devices; | 
|  | 726 | while (dev) { | 
|  | 727 | dev->fd = open(dev->device, mode); | 
|  | 728 | if (dev->fd < 0) { | 
|  | 729 | fprintf(stderr, "Unable to open log device '%s': %s\n", | 
|  | 730 | dev->device, strerror(errno)); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 731 | exit(EXIT_FAILURE); | 
|  | 732 | } | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 733 |  | 
|  | 734 | if (clearLog) { | 
|  | 735 | int ret; | 
|  | 736 | ret = android::clearLog(dev->fd); | 
|  | 737 | if (ret) { | 
|  | 738 | perror("ioctl"); | 
|  | 739 | exit(EXIT_FAILURE); | 
|  | 740 | } | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 741 | } | 
|  | 742 |  | 
|  | 743 | if (getLogSize) { | 
|  | 744 | int size, readable; | 
|  | 745 |  | 
|  | 746 | size = android::getLogSize(dev->fd); | 
|  | 747 | if (size < 0) { | 
|  | 748 | perror("ioctl"); | 
|  | 749 | exit(EXIT_FAILURE); | 
|  | 750 | } | 
|  | 751 |  | 
|  | 752 | readable = android::getLogReadableSize(dev->fd); | 
|  | 753 | if (readable < 0) { | 
|  | 754 | perror("ioctl"); | 
|  | 755 | exit(EXIT_FAILURE); | 
|  | 756 | } | 
|  | 757 |  | 
|  | 758 | printf("%s: ring buffer is %dKb (%dKb consumed), " | 
|  | 759 | "max entry is %db, max payload is %db\n", dev->device, | 
|  | 760 | size / 1024, readable / 1024, | 
|  | 761 | (int) LOGGER_ENTRY_MAX_LEN, (int) LOGGER_ENTRY_MAX_PAYLOAD); | 
|  | 762 | } | 
|  | 763 |  | 
|  | 764 | dev = dev->next; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 765 | } | 
|  | 766 |  | 
|  | 767 | if (getLogSize) { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 768 | return 0; | 
|  | 769 | } | 
| Joe Onorato | e2bf2ea | 2010-03-01 09:11:54 -0800 | [diff] [blame] | 770 | if (clearLog) { | 
|  | 771 | return 0; | 
|  | 772 | } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 773 |  | 
|  | 774 | //LOG_EVENT_INT(10, 12345); | 
|  | 775 | //LOG_EVENT_LONG(11, 0x1122334455667788LL); | 
|  | 776 | //LOG_EVENT_STRING(0, "whassup, doc?"); | 
|  | 777 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 778 | if (needBinary) | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 779 | android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE); | 
|  | 780 |  | 
| Joe Onorato | 6fa09a0 | 2010-02-26 10:04:23 -0800 | [diff] [blame] | 781 | android::readLogLines(devices); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 782 |  | 
|  | 783 | return 0; | 
|  | 784 | } |