Android Open Source - ubihelper Log Manager






From Project

Back to project page ubihelper.

License

The source code is released under:

GNU General Public License

If you think the Android project ubihelper listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * Copyright (c) 2012 The University of Nottingham
 * //  w w w .j av a  2  s.  c  o  m
 * This file is part of ubihelper
 *
 *  ubihelper is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  ubihelper is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with ubihelper. If not, see <http://www.gnu.org/licenses/>.
 *  
 *  @author Chris Greenhalgh (cmg@cs.nott.ac.uk), The University of Nottingham
 */

package uk.ac.horizon.ubihelper.service;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TreeSet;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Environment;
import android.os.StatFs;
import android.preference.PreferenceManager;
import android.util.Log;
import uk.ac.horizon.ubihelper.R;
import uk.ac.horizon.ubihelper.channel.ChannelManager;
import uk.ac.horizon.ubihelper.channel.Subscription;
import uk.ac.horizon.ubihelper.ui.LoggingPreferences;
import uk.ac.horizon.ubihelper.ui.PeerRequestActivity;

/**
 * @author cmg
 *
 */
public class LogManager {
  static final String TAG = "ubihelper-log";
  public static final String LOG = "log";
  public static final String LOG_PERIOD = "log_period";
  public static final String LOG_FILE_PREFIX = "log_file_prefix";
  public static final String LOG_MAX_FILE_SIZE = "log_max_file_size";
  public static final String LOG_MAX_CACHE_SIZE = "log_max_cache_size";
  public static final String LOG_CHANNELS = "log_channels";
  public static final String LOG_DELETE_OLD_FILES = "log_delete_old_files";
  public static final String PUBLIC_FILES_DIR = "UbihelperLogs";
  public static final String FILES_DIR = "logs";
  private static final int DEFAULT_PERIOD = 1000;
  private static final double MIN_PERIOD_CHANGE = 0.001;
  public static final int MIN_NEW_FILE_SIZE = 10000;
  private static final int BUFFER_SIZE = 10000;
  private static final int DEFAULT_FLUSH_DELAY = 1000;

  private Service service;
  private ChannelManager channelManager;
  private boolean logging = false;
  private HashMap<String,LogSubscription> subscriptions = new HashMap<String,LogSubscription>();
  private File currentLogDir;
  private File currentLogFile;
  private long currentFileLength;
  private long currentCacheUsage;
  private OutputStream currentLogStream;
  private int maxFileSize;
  private int maxCacheSize;
  private List<File> cacheFiles;
  private boolean flushPosted;
  private static int ERROR_NOTIFICATION_ID = 1001;
  private boolean errorNotificationVisible;
  private String errorNotificationText;
  
  public LogManager(Service service, ChannelManager channelManager) {
    this.service = service;
    this.channelManager = channelManager;

    // debug
//      Log.d(TAG, "These should work in all versions of Android:");
//      Log.d(TAG, "Environment.getExternalStorageDirectory() = " 
//          + Environment.getExternalStorageDirectory());
//      Log.d(TAG, "Environment.getDataDirectory() = " 
//          + Environment.getDataDirectory());
//      Log.d(TAG, "Environment.getDownloadCacheDirectory() = " 
//          + Environment.getDownloadCacheDirectory());
//      Log.d(TAG, "Environment.getRootDirectory() = " 
//          + Environment.getRootDirectory());
//
//      Log.d(TAG, "These were added in FroYo (SDK v8):");
//      Log.d(TAG,
//          "Environment.getExternalStoragePublicDirectory(PUBLIC_FILES_DIR) = " 
//              + Environment.getExternalStoragePublicDirectory(FILES_DIR));
//      Log.d(TAG, "getExternalCacheDir() = " + service.getExternalCacheDir());
//      Log.d(TAG, "getExternalFilesDir(null) = " 
//          + service.getExternalFilesDir(null));
//      Log.d(TAG,
//          "getExternalFilesDir(FILES_DIR) = " 
//              + service.getExternalFilesDir(FILES_DIR));

    maxFileSize = getMaxFileSize();
    maxCacheSize = getMaxCacheSize();
  }
  
  public synchronized void close() {
    if (logging) {
      logging = false;
      stop();
    }
    clearError();
  }  

  private static File getLogDirectory(Context context) {
    // may return null
    // Note: needs Write external storage permission
    return context.getExternalFilesDir(FILES_DIR);
  }
  private boolean getLog() {
    return PreferenceManager.getDefaultSharedPreferences(service).getBoolean(LOG, false);
  }
  private float getLogPeriod() {
    float period = DEFAULT_PERIOD;
    try {
      String value= PreferenceManager.getDefaultSharedPreferences(service).getString(LOG_PERIOD, Integer.toString(DEFAULT_PERIOD));
      period = Float.parseFloat(value);
    }
    catch (Exception e) {
      Log.e(TAG,"Error getting log_period preference: "+e);
    }
    return period;
  }
  private int getIntPreference(String name, int defaultValue) {
    int val= defaultValue;
    try {
      String sval= PreferenceManager.getDefaultSharedPreferences(service).getString(name, Integer.toString(defaultValue));
      val = Integer.parseInt(sval);
    }
    catch (Exception e) {
      Log.e(TAG,"Error getting "+name+" preference: "+e);
    }
    return val;
  }
  private int getMaxFileSize() {
    int size = getIntPreference(LOG_MAX_FILE_SIZE, 0);
    if (size<MIN_NEW_FILE_SIZE)
      size = MIN_NEW_FILE_SIZE;
    return size;
  }
  private int getMaxCacheSize() {
    int size = getIntPreference(LOG_MAX_CACHE_SIZE, 0);
    if (size<MIN_NEW_FILE_SIZE)
      size = MIN_NEW_FILE_SIZE;
    return size;
  }
  // called from Service when prefs changed
  public void checkPreferences(String changed) {
    if (LOG_CHANNELS.equals(changed) || LOG.equals(changed) || LOG_PERIOD.equals(changed))
      // may need immediate adjustment!
      checkPreferences();
    else if (LOG_FILE_PREFIX.equals(changed))
      // force re-open on next log event (will use new prefix)
      closeCurrentFile();
    else if (LOG_MAX_CACHE_SIZE.equals(changed))
      maxCacheSize = getMaxCacheSize();
    else if (LOG_MAX_FILE_SIZE.equals(changed))
      maxFileSize = getMaxFileSize();
  }
  
  private void closeCurrentFile() {
    boolean ok = true;
    if (currentLogStream!=null) {
      try {
        currentLogStream.flush();
        currentLogStream.close();
      }
      catch (Exception e) {
        Log.e(TAG,"Error closing currentLogStream: "+e);
        ok = false;
      }
      currentLogStream = null;
    }
    if (currentLogFile!=null) {
      try {
        currentCacheUsage += currentLogFile.length();
      }
      catch (Exception e) {
        Log.e(TAG,"Error updating cache usage on closeCurrentFile: "+e);
        ok = false;
      }
      currentLogFile = null;
      currentFileLength = 0;
    }
    if (!ok) {
      // reset
      currentLogDir = null;
      currentLogFile = null;
      currentCacheUsage = 0;
      currentFileLength = 0;
    }
  }

  // called from Service on start-up (and from change of prefs) 
  public synchronized void checkPreferences() {
    boolean log = getLog();
    if (!log && logging) {
      logging = false;
      stop();
    }
    if (log && !logging) {
      logging = true;
      start();
    }
    else if (logging)
      checkSubscriptions();
  }
  private String[] getChannels() {
    String cnpref = PreferenceManager.getDefaultSharedPreferences(service).getString(LOG_CHANNELS, "");
    Log.d(TAG,"log_channels="+cnpref);
    String cns[] = cnpref.split(";");
    return cns;
  }

  private synchronized void checkSubscriptions() {
    float period = getLogPeriod();
    String cns[] = getChannels();
    TreeSet<String> ecns = new TreeSet<String>();
    ecns.addAll(subscriptions.keySet());
    for (int i=0; i<cns.length; i++) {
      String cn = cns[i];
      if (cn.length()==0)
        continue;
      ecns.remove(cn);
      LogSubscription sub = subscriptions.get(cn);
      if (sub==null) {
        // add
        Log.d(TAG,"Start logging "+cn+" (update)");
        sub = new LogSubscription(this, cn);
        sub.updateConfiguration(period, period/2, 0);
        channelManager.addSubscription(sub);
        subscriptions.put(cn, sub);
        channelManager.refreshChannel(cn);
      } else {
        // update
        if (Math.abs(sub.getPeriod()-period)>MIN_PERIOD_CHANGE) {
          Log.d(TAG,"Update period for "+cn+" (update)");
          sub.updateConfiguration(period, period/2, 0);
          channelManager.refreshChannel(cn);
        }
      }
    }
    // remove?
    for (String cn : ecns) {
      Log.d(TAG,"Stop logging "+cn+" (update)");
      LogSubscription sub = subscriptions.remove(cn);
      if (sub!=null) 
        channelManager.removeSubscription(sub);
      channelManager.refreshChannel(cn);
    }
  }

  private synchronized void stop() {
    closeCurrentFile();
    for (Map.Entry<String,LogSubscription> e : subscriptions.entrySet()) {
      Log.d(TAG,"Stop logging "+e.getKey()+" (stop all)");
      channelManager.removeSubscription(e.getValue());
      channelManager.refreshChannel(e.getKey());
    }
    subscriptions.clear();
  }

  private synchronized void start() {
    float period = getLogPeriod();
    String cns[] = getChannels();
    for (int i=0; i<cns.length; i++) {
      String cn = cns[i];
      if (cn.length()==0)
        continue;
      Log.d(TAG,"Start logging "+cn+" (start all)");
      LogSubscription sub = new LogSubscription(this, cn);
      sub.updateConfiguration(period, period/2, 0);
      channelManager.addSubscription(sub);
      subscriptions.put(cn, sub);
      channelManager.refreshChannel(cn);
    }
  }

  // called from LogSubscription
  public synchronized void logValue(String channelName, JSONObject value) {
    if (!logging) {
      Log.d(TAG,"ignore logValue (not logging) "+channelName+"="+value);
      return;
    }
    Log.d(TAG,"logValue "+channelName+"="+value);

    // marshall
    long time = System.currentTimeMillis();
    JSONObject lval = new JSONObject();
    byte data[];
    try {
      lval.put("time", time);
      lval.put("name", channelName);
      lval.put("value", value);
      data = lval.toString().getBytes("UTF-8");
    } catch (Exception e) {
      Log.e(TAG,"marshalling log value: "+e);
      return;
    }    
    
    try {
      // quick write?
      if (writeValue(data))
        return;
    }
    catch (Exception e) {
      Log.w(TAG,"problem writing value: "+e);
    }
    // ensure no open file
    closeCurrentFile();

    // fix up...
    File dir = getLogDirectory(service);
    if (dir==null || !dir.exists() || !dir.canWrite()) {
      signalError("External storage not accessible/present");
      return;
    }
    if (currentLogDir==null && !dir.equals(currentLogDir)) {
      // initialise log dir, i.e. current size
      currentLogDir = dir;
      currentLogDir.mkdirs();
      try {
        cacheFiles = new ArrayList<File>();
        File files[] = currentLogDir.listFiles();
        for (int fi=0; fi<files.length; fi++) 
          cacheFiles.add(files[fi]);
        currentCacheUsage = 0;
        for (File f : cacheFiles)
          currentCacheUsage += f.length();
        Collections.sort(cacheFiles, new Comparator<File>() {
          public int compare(File f1, File f2) {
            return new Long(f1.lastModified()).compareTo(f2.lastModified());
          }
        });
      }
      catch (Exception e) {
        signalError("External storage not accessible/present (on check cache size)");
        return;
      }
    }
    // current log configuration
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(service);

    // is there space 
    StatFs fs = new StatFs(currentLogDir.getAbsolutePath());
    long availableFs = fs.getAvailableBlocks()*fs.getBlockSize();
    long availableCache = (maxCacheSize>0) ? maxCacheSize-currentCacheUsage : availableFs;
    
    if (availableCache < MIN_NEW_FILE_SIZE) {
      boolean deleteOldFiles = prefs.getBoolean(LOG_DELETE_OLD_FILES, false);
      if (!deleteOldFiles) {
        if (availableCache<availableFs)
          signalError("Log cache full (cannot delete old files)");
        else
          signalError("Log storage device full (cannot delete old files)");
        return;
      }
      // try deleting oldest file first
      while (cacheFiles.size()>0 && availableCache < MIN_NEW_FILE_SIZE) {
        File f = cacheFiles.remove(0);
        Log.i(TAG,"Deleting old cache file "+f+" (modified "+logDateFormat.format(new Date(f.lastModified()))+", length "+f.length()+")");

        long len = f.length();
        currentCacheUsage -= len;
        availableCache += len;
        if (!f.delete()) {
          signalError("Log cache full (failed to delete old file(s))");
          return;
        }
      }
      if (availableCache < MIN_NEW_FILE_SIZE) {
        signalError("Log cache full (no old files)");
        return;
      }
    }
    
    // new log file
    String filePrefix = prefs.getString(LOG_FILE_PREFIX, "log");
    
    int i=1;
    String filename = filePrefix+"_"+logDateFormat.format(new Date(time));
    currentLogFile = new File(currentLogDir, filename+".log");
    while (currentLogFile.exists()) {
      i++;
      currentLogFile = new File(currentLogDir, filename+"_"+i+".log");
    }
    
    try {
      OutputStream os = new FileOutputStream(currentLogFile);
      currentLogStream = new BufferedOutputStream(os, BUFFER_SIZE);
      cacheFiles.add(currentLogFile);
      currentFileLength = 0;
    }
    catch (Exception e) {
      Log.w(TAG,"Error opening log file "+currentLogFile+": "+e);
      signalError("Cannot create log file");
      closeCurrentFile();
      return;
    }
    
    // try again!
    try {
      // quick write?
      if (writeValue(data))
        return;
    }
    catch (Exception e) {
      Log.w(TAG,"problem writing value: "+e);
    }
    signalError("Cannot write to new log file");
    return;
  }

  private static SimpleDateFormat logDateFormat = new SimpleDateFormat("yyyy-MM-dd_HHmmss-SSS");
  
  private void signalError(String error) {
    // force re-check...
    currentLogDir = null;
    if (errorNotificationVisible && error.equals(errorNotificationText))
      return;
    errorNotificationText = error;
    
    int icon = R.drawable.log_error_notification_icon;
    CharSequence tickerText = error;
    long when = System.currentTimeMillis();

    Notification notification = new Notification(icon, tickerText, when);
    
    Context context = service;
    CharSequence contentTitle = "Ubihelper Logging";
    CharSequence contentText = error;
    Intent notificationIntent = new Intent(LoggingPreferences.INTENT);
    PendingIntent contentIntent = PendingIntent.getActivity(service, 0, notificationIntent, 0);

    notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
    NotificationManager mNotificationManager = (NotificationManager) service.getSystemService(Service.NOTIFICATION_SERVICE);
    //service.(ci.notificationId, notification);  
    mNotificationManager.notify(ERROR_NOTIFICATION_ID, notification);
    errorNotificationVisible = true;
  }

  private static byte nl[] = new byte[] { (byte)'\n' };
  private synchronized boolean writeValue(byte[] data) throws IOException {
    if (currentLogStream==null)
      return false;

    int size = data.length+nl.length;
    if ((maxFileSize>0 && currentFileLength>=maxFileSize) ||
        (maxCacheSize>0 && currentFileLength+size+currentCacheUsage >= maxCacheSize)) {
      closeCurrentFile();
      return false;
    }
    
    currentLogStream.write(data);
    currentLogStream.write(nl);
    currentFileLength += size;
    
    if (!flushPosted) {
      service.postTaskDelayed(flushTask, DEFAULT_FLUSH_DELAY);
      flushPosted = true;
    }
    
    clearError();
    
    if (maxFileSize>0 && currentFileLength>=maxFileSize)
      // fast close
      closeCurrentFile();
    
    return true;
  }
  private Runnable flushTask = new Runnable() {
    public void run() {
      flushPosted = false;
      flush();
    }
  };
  private void clearError() {
    if (!errorNotificationVisible)
      return;
    NotificationManager mNotificationManager = (NotificationManager) service.getSystemService(Service.NOTIFICATION_SERVICE);
    mNotificationManager.cancel(ERROR_NOTIFICATION_ID);    
    errorNotificationVisible = false;
  }
  private synchronized void flush() {
    if (currentLogStream!=null) {
      try {
        currentLogStream.flush();
      }
      catch (Exception e) {
        Log.w(TAG,"Error flushing current stream: "+e);
        closeCurrentFile();
        signalError("Cannot persist values to log file");
      }
    }
  }
}




Java Source Code List

org.json.JSONArray.java
org.json.JSONException.java
org.json.JSONObject.java
org.json.JSONString.java
org.json.JSONStringer.java
org.json.JSONTokener.java
org.json.JSONWriter.java
uk.ac.horizon.ubihelper.channel.ChannelFactory.java
uk.ac.horizon.ubihelper.channel.ChannelListener.java
uk.ac.horizon.ubihelper.channel.ChannelManager.java
uk.ac.horizon.ubihelper.channel.ChannelValueEvent.java
uk.ac.horizon.ubihelper.channel.NamedChannel.java
uk.ac.horizon.ubihelper.channel.PullSubscription.java
uk.ac.horizon.ubihelper.channel.SharedVariableChannel.java
uk.ac.horizon.ubihelper.channel.Subscription.java
uk.ac.horizon.ubihelper.dns.DnsClient.java
uk.ac.horizon.ubihelper.dns.DnsProtocol.java
uk.ac.horizon.ubihelper.dns.DnsServer.java
uk.ac.horizon.ubihelper.dns.DnsUtils.java
uk.ac.horizon.ubihelper.httpserver.HttpClientHandler.java
uk.ac.horizon.ubihelper.httpserver.HttpContinuation.java
uk.ac.horizon.ubihelper.httpserver.HttpError.java
uk.ac.horizon.ubihelper.httpserver.HttpListener.java
uk.ac.horizon.ubihelper.j2se.Base64.java
uk.ac.horizon.ubihelper.j2se.Server.java
uk.ac.horizon.ubihelper.net.Fragment.java
uk.ac.horizon.ubihelper.net.Marshaller.java
uk.ac.horizon.ubihelper.net.Message.java
uk.ac.horizon.ubihelper.net.OnPeerConnectionListener.java
uk.ac.horizon.ubihelper.net.PeerConnectionScheduler.java
uk.ac.horizon.ubihelper.net.PeerConnection.java
uk.ac.horizon.ubihelper.protocol.ClientInfo.java
uk.ac.horizon.ubihelper.protocol.ClientState.java
uk.ac.horizon.ubihelper.protocol.MessageUtils.java
uk.ac.horizon.ubihelper.protocol.PeerInfo.java
uk.ac.horizon.ubihelper.protocol.ProtocolManager.java
uk.ac.horizon.ubihelper.service.BroadcastIntentSubscription.java
uk.ac.horizon.ubihelper.service.EnabledPeersChannel.java
uk.ac.horizon.ubihelper.service.LogManager.java
uk.ac.horizon.ubihelper.service.LogSubscription.java
uk.ac.horizon.ubihelper.service.PeerManager.java
uk.ac.horizon.ubihelper.service.PeersOpenHelper.java
uk.ac.horizon.ubihelper.service.Service.java
uk.ac.horizon.ubihelper.service.WifiDiscoveryManager.java
uk.ac.horizon.ubihelper.service.channel.BluetoothDiscoveryChannel.java
uk.ac.horizon.ubihelper.service.channel.CellLocationChannel.java
uk.ac.horizon.ubihelper.service.channel.CellStrengthChannel.java
uk.ac.horizon.ubihelper.service.channel.GpsStatusChannel.java
uk.ac.horizon.ubihelper.service.channel.LocationChannel.java
uk.ac.horizon.ubihelper.service.channel.MicChannel.java
uk.ac.horizon.ubihelper.service.channel.PollingChannel.java
uk.ac.horizon.ubihelper.service.channel.SensorChannel.java
uk.ac.horizon.ubihelper.service.channel.TimeChannel.java
uk.ac.horizon.ubihelper.service.channel.WifiScannerChannel.java
uk.ac.horizon.ubihelper.ui.AboutActivity.java
uk.ac.horizon.ubihelper.ui.ChannelListActivity.java
uk.ac.horizon.ubihelper.ui.ChannelPeerListActivity.java
uk.ac.horizon.ubihelper.ui.ChannelValueActivity.java
uk.ac.horizon.ubihelper.ui.ChannelViewActivity.java
uk.ac.horizon.ubihelper.ui.LoggingChannelListActivity.java
uk.ac.horizon.ubihelper.ui.LoggingPreferences.java
uk.ac.horizon.ubihelper.ui.MainPreferences.java
uk.ac.horizon.ubihelper.ui.ManagePeersActivity.java
uk.ac.horizon.ubihelper.ui.PeerInfoActivity.java
uk.ac.horizon.ubihelper.ui.PeerManualAddActivity.java
uk.ac.horizon.ubihelper.ui.PeerRequestActivity.java
uk.ac.horizon.ubihelper.ui.PeerRequestInfoActivity.java
uk.ac.horizon.ubihelper.ui.SearchPeersActivity.java
uk.ac.horizon.ubihelper.ui.TestActivity.java
uk.ac.horizon.ubihelper.ui.WifiStatusActivity.java
uk.ac.horizon.ubihelper.websocket.ClientWebsocket.java
uk.ac.horizon.ubihelper.websocket.ReadyState.java
uk.ac.horizon.ubihelper.websocket.SocketChannelWebsocket.java
uk.ac.horizon.ubihelper.websocket.WebsocketListener.java
uk.ac.horizon.ubihelper.websocket.Websocket.java