org.opendatakit.common.android.utilities.WebLogger.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.common.android.utilities.WebLogger.java

Source

/*
 * Copyright (C) 2012-2013 University of Washington
 *
 * 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.
 */

package org.opendatakit.common.android.utilities;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang3.CharEncoding;
import org.opendatakit.common.android.utilities.StaticStateManipulator.IStaticFieldManipulator;

import android.util.Log;

/**
 * Logger that emits logs to the LOGGING_PATH and recycles them as needed.
 * Useful to separate out ODK log entries from the overall logging stream,
 * especially on heavily logged 4.x systems.
 *
 * @author mitchellsundt@gmail.com
 */
public class WebLogger {
    private static final long MILLISECONDS_DAY = 86400000L;
    private static final long FLUSH_INTERVAL = 12000L; // 5 times a minute

    private static final int ASSERT = 1;
    private static final int VERBOSE = 2;
    private static final int DEBUG = 3;
    private static final int INFO = 4;
    private static final int WARN = 5;
    private static final int ERROR = 6;
    private static final int SUCCESS = 7;
    private static final int TIP = 8;

    private static final int LOG_INFO_LEVEL = 1;

    private static long lastStaleScan = 0L;
    private static Map<String, WebLogger> loggers = new HashMap<String, WebLogger>();

    static {
        // register a state-reset manipulator for 'loggers' field.
        StaticStateManipulator.get().register(99, new IStaticFieldManipulator() {

            @Override
            public void reset() {
                for (WebLogger l : loggers.values()) {
                    l.close();
                }
                loggers.clear();
            }

        });
    }

    /**
     * Instance variables
     */

    // appName under which to write log
    private final String appName;
    // dateStamp (filename) of opened stream
    private String dateStamp = null;
    // opened stream
    private OutputStreamWriter logFile = null;
    // the last time we flushed our output stream
    private long lastFlush = 0L;

    private static class ThreadLogger extends ThreadLocal<String> {

        @Override
        protected String initialValue() {
            return null;
        }

    }

    private static ThreadLogger contextLogger = new ThreadLogger();

    public static WebLogger getContextLogger() {
        String appNameOfThread = contextLogger.get();
        if (appNameOfThread != null) {
            return getLogger(appNameOfThread);
        }
        return null;
    }

    public synchronized static WebLogger getLogger(String appName) {
        WebLogger logger = loggers.get(appName);
        if (logger == null) {
            logger = new WebLogger(appName);
            loggers.put(appName, logger);
        }

        contextLogger.set(appName);

        long now = System.currentTimeMillis();
        if (lastStaleScan + MILLISECONDS_DAY < now) {
            try {
                // scan for stale logs...
                String loggingPath = ODKFileUtils.getLoggingFolder(appName);
                final long distantPast = now - 30L * MILLISECONDS_DAY; // thirty days
                                                                       // ago...
                File loggingDirectory = new File(loggingPath);
                loggingDirectory.mkdirs();

                File[] stale = loggingDirectory.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        return (pathname.lastModified() < distantPast);
                    }
                });

                if (stale != null) {
                    for (File f : stale) {
                        f.delete();
                    }
                }
            } catch (Exception e) {
                // no exceptions are claimed, but since we can mount/unmount
                // the SDCard, there might be an external storage unavailable
                // exception that would otherwise percolate up.
                e.printStackTrace();
            } finally {
                // whether or not we failed, record that we did the scan.
                lastStaleScan = now;
            }
        }
        return logger;
    }

    private WebLogger(String appName) {
        this.appName = appName;
    }

    private synchronized void close() {
        if (logFile != null) {
            OutputStreamWriter writer = logFile;
            logFile = null;
            try {
                writer.flush();
                writer.close();
            } catch (IOException e) {
                Log.e("WebLogger", "Unable to flush and close " + appName + " WebLogger");
            }
        }
    }

    private synchronized void log(String logMsg) throws IOException {
        String curDateStamp = (new SimpleDateFormat("yyyy-MM-dd_HH", Locale.ENGLISH)).format(new Date());
        if (logFile == null || dateStamp == null || !curDateStamp.equals(dateStamp)) {
            // the file we should log to has changed.
            // or has not yet been opened.

            if (logFile != null) {
                // close existing writer...
                OutputStreamWriter writer = logFile;
                logFile = null;
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            String loggingPath = ODKFileUtils.getLoggingFolder(appName);
            File loggingDirectory = new File(loggingPath);
            if (!loggingDirectory.exists()) {
                if (!loggingDirectory.mkdirs()) {
                    Log.e("WebLogger", "Unable to create logging directory");
                    return;
                }
            }

            if (!loggingDirectory.isDirectory()) {
                Log.e("WebLogger", "Logging Directory exists but is not a directory!");
                return;
            }

            File f = new File(loggingDirectory, curDateStamp + ".log");
            try {
                FileOutputStream fo = new FileOutputStream(f, true);
                logFile = new OutputStreamWriter(new BufferedOutputStream(fo), CharEncoding.UTF_8);
                dateStamp = curDateStamp;
                // if we see a lot of these being logged, we have a problem
                logFile.write("---- starting ----\n");
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("WebLogger", "Unexpected exception while opening logging file: " + e.toString());
                try {
                    if (logFile != null) {
                        logFile.close();
                    }
                } catch (Exception ex) {
                    // ignore
                }
                logFile = null;
                return;
            }
        }

        if (logFile != null) {
            logFile.write(logMsg + "\n");
        }

        if (lastFlush + WebLogger.FLUSH_INTERVAL < System.currentTimeMillis()) {
            // log when we are explicitly flushing, just to have a record of that in the log
            logFile.write("---- flushing ----\n");
            logFile.flush();
            lastFlush = System.currentTimeMillis();
        }
    }

    public void log(int severity, String t, String logMsg) {
        try {
            // do logcat logging...
            if (severity == ERROR) {
                Log.e(t, logMsg);
            } else if (severity == WARN) {
                Log.w(t, logMsg);
            } else if (LOG_INFO_LEVEL >= severity) {
                Log.i(t, logMsg);
            } else {
                Log.d(t, logMsg);
            }
            // and compose the log to the file...
            switch (severity) {
            case ASSERT:
                logMsg = "A/" + t + ": " + logMsg;
                break;
            case DEBUG:
                logMsg = "D/" + t + ": " + logMsg;
                break;
            case ERROR:
                logMsg = "E/" + t + ": " + logMsg;
                break;
            case INFO:
                logMsg = "I/" + t + ": " + logMsg;
                break;
            case SUCCESS:
                logMsg = "S/" + t + ": " + logMsg;
                break;
            case VERBOSE:
                logMsg = "V/" + t + ": " + logMsg;
                break;
            case TIP:
                logMsg = "T/" + t + ": " + logMsg;
                break;
            case WARN:
                logMsg = "W/" + t + ": " + logMsg;
                break;
            default:
                Log.d(t, logMsg);
                logMsg = "?/" + t + ": " + logMsg;
                break;
            }
            log(logMsg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void a(String t, String logMsg) {
        log(ASSERT, t, logMsg);
    }

    public void t(String t, String logMsg) {
        log(TIP, t, logMsg);
    }

    public void v(String t, String logMsg) {
        log(VERBOSE, t, logMsg);
    }

    public void d(String t, String logMsg) {
        log(DEBUG, t, logMsg);
    }

    public void i(String t, String logMsg) {
        log(INFO, t, logMsg);
    }

    public void w(String t, String logMsg) {
        log(WARN, t, logMsg);
    }

    public void e(String t, String logMsg) {
        log(ERROR, t, logMsg);
    }

    public void printStackTrace(Throwable e) {
        e.printStackTrace();
        ByteArrayOutputStream ba = new ByteArrayOutputStream();
        PrintStream w;
        try {
            w = new PrintStream(ba, false, "UTF-8");
            e.printStackTrace(w);
            w.flush();
            w.close();
            log(ba.toString("UTF-8"));
        } catch (UnsupportedEncodingException e1) {
            // error if it ever occurs
            throw new IllegalStateException("unable to specify UTF-8 Charset!");
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }

    public void s(String t, String logMsg) {
        log(SUCCESS, t, logMsg);
    }

}