1 /******************************************************************************* 2 * Copyright (C) 2013 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 *******************************************************************************/ 17 18 package com.android.mail; 19 20 import com.android.mail.utils.LogTag; 21 import com.android.mail.utils.LogUtils; 22 23 import android.app.Service; 24 import android.content.Intent; 25 import android.os.IBinder; 26 import android.util.Pair; 27 28 import java.io.FileDescriptor; 29 import java.io.PrintWriter; 30 import java.util.Date; 31 import java.util.HashMap; 32 import java.util.LinkedList; 33 import java.util.Map; 34 import java.util.Queue; 35 36 /** 37 * A write-only device for sensitive logs. Turned on only during debugging. 38 * 39 * Dump valuable system state by sending a local broadcast to the associated activity. 40 * Broadcast receivers are responsible for dumping state as they see fit. 41 * This service is only started when the log level is high, so there is no risk of user 42 * data being logged by mistake. 43 * 44 * To add logging to this service, call {@link #log(String, String, Object...)} with a tag name, 45 * which is a class name, like "AbstractActivityController", which is a unique ID. Then, add to the 46 * resulting buffer any information of interest at logging time. This is kept in a ring buffer, 47 * which is overwritten with new information. 48 */ 49 public class MailLogService extends Service { 50 /** 51 * This is the top level flag that enables this service. 52 */ 53 public static boolean DEBUG_ENABLED = false; 54 55 /** The tag which needs to be turned to DEBUG to get logging going. */ 56 protected static final String LOG_TAG = LogTag.getLogTag(); 57 58 /** 59 * A circular buffer of {@value #SIZE} lines. To insert into this buffer, 60 * call the {@link #put(String)} method. To retrieve the most recent logs, 61 * call the {@link #toString()} method. 62 */ 63 private static class CircularBuffer { 64 // We accept fifty lines of input. 65 public static final int SIZE = 50; 66 /** The actual list of strings to be printed. */ 67 final Queue<Pair<Long, String>> mList = new LinkedList<Pair<Long, String>>(); 68 /** The current size of the buffer */ 69 int mCurrentSize = 0; 70 71 /** Create an empty log buffer. */ CircularBuffer()72 private CircularBuffer() { 73 // Do nothing 74 } 75 76 /** Get the current timestamp */ dateToString(long timestamp)77 private static String dateToString(long timestamp) { 78 final Date d = new Date(timestamp); 79 return String.format("%d-%d %d:%d:%d: ", d.getDay(), d.getMonth(), d.getHours(), 80 d.getMinutes(), d.getSeconds()); 81 } 82 83 /** 84 * Insert a log message into the buffer. This might evict the oldest message if the log 85 * is at capacity. 86 * @param message a log message for this buffer. 87 */ put(String message)88 private synchronized void put(String message) { 89 if (mCurrentSize == SIZE) { 90 // At capacity, we'll remove the head, and add to the tail. Size is unchanged. 91 mList.remove(); 92 } else { 93 // Less than capacity. Adding a new element at the end. 94 mCurrentSize++; 95 } 96 // Add the current timestamp along with the message. 97 mList.add(new Pair<Long, String>(System.currentTimeMillis(), message)); 98 } 99 100 @Override toString()101 public String toString() { 102 final StringBuilder builder = new StringBuilder(); 103 for (final Pair<Long, String> s : mList) { 104 // Print the timestamp as an actual date, and then the message. 105 builder.append(dateToString(s.first)); 106 builder.append(s.second); 107 // Put a newline at the end of each log line. 108 builder.append("\n"); 109 } 110 return builder.toString(); 111 } 112 } 113 114 /** Header printed at the start of the dump. */ 115 private static final String HEADER = "**** MailLogService ***\n"; 116 /** Map of current tag -> log. */ 117 private static final Map<String, CircularBuffer> sLogs = new HashMap<String, CircularBuffer>(); 118 119 @Override onBind(Intent intent)120 public IBinder onBind(Intent intent) { 121 return null; 122 } 123 124 /** 125 * Return the circular buffer associated with this tag, or create a new buffer if none is 126 * currently associated. 127 * @param tag a string to identify a unique tag. 128 * @return a circular buffer associated with a string tag. 129 */ getOrCreate(String tag)130 private static CircularBuffer getOrCreate(String tag) { 131 if (sLogs.containsKey(tag)) { 132 return sLogs.get(tag); 133 } 134 // Create a new CircularBuffer with this tag 135 final CircularBuffer buffer = new CircularBuffer(); 136 sLogs.put(tag, buffer); 137 return buffer; 138 } 139 140 /** 141 * Return true if the logging level is high enough for this service to function. 142 * @return true if this service is functioning at the current log level. False otherwise. 143 */ isLoggingLevelHighEnough()144 public static boolean isLoggingLevelHighEnough() { 145 return LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG); 146 } 147 148 /** 149 * Add to the log for the tag given. 150 * @param tag a unique tag to add the message to 151 * @param format a string format for the message 152 * @param args optional list of arguments for the format. 153 */ log(String tag, String format, Object... args)154 public static void log(String tag, String format, Object... args) { 155 if (!DEBUG_ENABLED || !isLoggingLevelHighEnough()) { 156 return; 157 } 158 // The message we are printing. 159 final String logMessage = String.format(format, args); 160 // Find the circular buffer to go with this tag, or create a new one. 161 getOrCreate(tag).put(logMessage); 162 } 163 164 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)165 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 166 if (!DEBUG_ENABLED) { 167 return; 168 } 169 writer.print(HEADER); 170 // Go through all the tags, and write them all out sequentially. 171 for (final String tag : sLogs.keySet()) { 172 // Write out a sub-header: Logging for tag "MyModuleName" 173 writer.append("Logging for tag: \""); 174 writer.append(tag); 175 writer.append("\"\n"); 176 177 writer.append(sLogs.get(tag).toString()); 178 } 179 // Go through all the buffers. 180 super.dump(fd, writer,args); 181 } 182 } 183