Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 50d756f7 authored by David Lin's avatar David Lin Committed by Android (Google) Code Review
Browse files

Merge changes from topic "satlog" into 24D1-dev

* changes:
  Allow enable/disablement of logging
  Create persistent logger for Satellite SOS
parents a5c31b95 922f161d
Loading
Loading
Loading
Loading
+261 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */

package android.telephony;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.DropBoxManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;

import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

/**
 * A persistent logger backend that stores logs in Android DropBoxManager
 *
 * @hide
 */
public class DropBoxManagerLoggerBackend implements PersistentLoggerBackend {

    private static final String TAG = "DropBoxManagerLoggerBackend";
    // Separate tag reference to be explicitly used for dropboxmanager instead of logcat logging
    private static final String DROPBOX_TAG = "DropBoxManagerLoggerBackend";
    private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER =
            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS");
    private static final ZoneId LOCAL_ZONE_ID = ZoneId.systemDefault();
    private static final int BUFFER_SIZE_BYTES = 500 * 1024; // 500 KB
    private static final int MIN_BUFFER_BYTES_FOR_FLUSH = 5 * 1024; // 5 KB

    private static DropBoxManagerLoggerBackend sInstance;

    private final DropBoxManager mDropBoxManager;
    private final Object mBufferLock = new Object();
    @GuardedBy("mBufferLock")
    private final StringBuilder mLogBuffer = new StringBuilder();
    private long mBufferStartTime = -1L;
    private final HandlerThread mHandlerThread = new HandlerThread(DROPBOX_TAG);
    private final Handler mHandler;
    // Flag for determining if logging is enabled as a general feature
    private final boolean mDropBoxManagerLoggingEnabled;
    // Flag for controlling if logging is enabled at runtime
    private boolean mIsLoggingEnabled = false;

    /**
     * Returns a singleton instance of {@code DropBoxManagerLoggerBackend} that will log to
     * DropBoxManager if the config_dropboxmanager_persistent_logging_enabled resource config is
     * enabled.
     * @param context Android context
     */
    @Nullable
    public static synchronized DropBoxManagerLoggerBackend getInstance(@NonNull Context context) {
        if (sInstance == null) {
            sInstance = new DropBoxManagerLoggerBackend(context);
        }
        return sInstance;
    }

    private DropBoxManagerLoggerBackend(@NonNull Context context) {
        mDropBoxManager = context.getSystemService(DropBoxManager.class);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        mDropBoxManagerLoggingEnabled = persistentLoggingEnabled(context);
    }

    private boolean persistentLoggingEnabled(@NonNull Context context) {
        try {
            return context.getResources().getBoolean(
                    R.bool.config_dropboxmanager_persistent_logging_enabled);
        } catch (RuntimeException e) {
            Log.w(TAG, "Persistent logging config not found");
            return false;
        }
    }

    /**
     * Enable or disable logging to DropBoxManager
     * @param isLoggingEnabled Whether logging should be enabled
     */
    public void setLoggingEnabled(boolean isLoggingEnabled) {
        Log.i(DROPBOX_TAG, "toggle logging: " + isLoggingEnabled);
        mIsLoggingEnabled = isLoggingEnabled;
    }

    /**
     * Persist a DEBUG log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void debug(@NonNull String tag, @NonNull String msg) {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }
        bufferLog("D", tag, msg, Optional.empty());
    }

    /**
     * Persist a INFO log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void info(@NonNull String tag, @NonNull String msg) {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }
        bufferLog("I", tag, msg, Optional.empty());
    }

    /**
     * Persist a WARN log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void warn(@NonNull String tag, @NonNull String msg) {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }
        bufferLog("W", tag, msg, Optional.empty());
    }

    /**
     * Persist a WARN log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param t An exception to log.
     */
    public void warn(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }
        bufferLog("W", tag, msg, Optional.of(t));
    }

    /**
     * Persist a ERROR log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void error(@NonNull String tag, @NonNull String msg) {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }
        bufferLog("E", tag, msg, Optional.empty());
    }

    /**
     * Persist a ERROR log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param t An exception to log.
     */
    public void error(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }
        bufferLog("E", tag, msg, Optional.of(t));
    }

    private synchronized void bufferLog(
            @NonNull String level,
            @NonNull String tag,
            @NonNull String msg,
            Optional<Throwable> t) {
        if (!mIsLoggingEnabled) {
            return;
        }

        if (mBufferStartTime == -1L) {
            mBufferStartTime = System.currentTimeMillis();
        }

        synchronized (mBufferLock) {
            mLogBuffer
                    .append(formatLog(level, tag, msg, t))
                    .append("\n");

            if (mLogBuffer.length() >= BUFFER_SIZE_BYTES) {
                flushAsync();
            }
        }
    }

    private String formatLog(
            @NonNull String level,
            @NonNull String tag,
            @NonNull String msg,
            Optional<Throwable> t) {
        // Expected format = "$Timestamp $Level $Tag: $Message"
        return formatTimestamp(System.currentTimeMillis()) + " " + level + " " + tag + ": "
                + t.map(throwable -> msg + ": " + Log.getStackTraceString(throwable)).orElse(msg);
    }

    private String formatTimestamp(long currentTimeMillis) {
        return Instant.ofEpochMilli(currentTimeMillis)
                .atZone(LOCAL_ZONE_ID)
                .format(LOG_TIMESTAMP_FORMATTER);
    }

    /**
     * Flushes all buffered logs into DropBoxManager as a single log record with a tag of
     * {@link #DROPBOX_TAG} asynchronously. Should be invoked sparingly as DropBoxManager has
     * device-level limitations on the number files that can be stored.
     */
    public void flushAsync() {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }

        mHandler.post(this::flush);
    };

    /**
     * Flushes all buffered logs into DropBoxManager as a single log record with a tag of
     * {@link #DROPBOX_TAG}. Should be invoked sparingly as DropBoxManager has device-level
     * limitations on the number files that can be stored.
     */
    public void flush() {
        if (!mDropBoxManagerLoggingEnabled) {
            return;
        }

        synchronized (mBufferLock) {
            if (mLogBuffer.length() < MIN_BUFFER_BYTES_FOR_FLUSH) {
                return;
            }

            Log.d(DROPBOX_TAG, "Flushing logs from "
                    + formatTimestamp(mBufferStartTime) + " to "
                    + formatTimestamp(System.currentTimeMillis()));

            try {
                mDropBoxManager.addText(DROPBOX_TAG, mLogBuffer.toString());
            } catch (Exception e) {
                Log.w(DROPBOX_TAG, "Failed to flush logs of length "
                        + mLogBuffer.length() + " to DropBoxManager", e);
            }
            mLogBuffer.setLength(0);
        }
        mBufferStartTime = -1L;
    }
}
+90 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */

package android.telephony;

import android.annotation.NonNull;

/**
 * A persistent logging client. Intended for persisting SOS/emergency related critical debug logs
 * in situations where standard Android logcat logs may not be retained long enough or available
 * for access.
 *
 * @hide
 */
public class PersistentLogger {
    private final PersistentLoggerBackend mPersistentLoggerBackend;

    public PersistentLogger(@NonNull PersistentLoggerBackend persistentLoggerBackend) {
        mPersistentLoggerBackend = persistentLoggerBackend;
    }

    /**
     * Persist a DEBUG log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void debug(@NonNull String tag, @NonNull String msg) {
        mPersistentLoggerBackend.debug(tag, msg);
    }

    /**
     * Persist a INFO log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void info(@NonNull String tag, @NonNull String msg) {
        mPersistentLoggerBackend.info(tag, msg);
    }

    /**
     * Persist a WARN log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void warn(@NonNull String tag, @NonNull String msg) {
        mPersistentLoggerBackend.warn(tag, msg);
    }

    /**
     * Persist a WARN log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param t An exception to log.
     */
    public void warn(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
        mPersistentLoggerBackend.warn(tag, msg, t);
    }

    /**
     * Persist a ERROR log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    public void error(@NonNull String tag, @NonNull String msg) {
        mPersistentLoggerBackend.error(tag, msg);
    }

    /**
     * Persist a ERROR log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param t An exception to log.
     */
    public void error(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
        mPersistentLoggerBackend.error(tag, msg, t);
    }
}
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */

package android.telephony;

import android.annotation.NonNull;

/**
 * Interface for logging backends to provide persistent log storage.
 *
 * @hide
 */
public interface PersistentLoggerBackend {

    /**
     * Persist a DEBUG log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    void debug(@NonNull String tag, @NonNull String msg);

    /**
     * Persist a INFO log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    void info(@NonNull String tag, @NonNull String msg);

    /**
     * Persist a WARN log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    void warn(@NonNull String tag, @NonNull String msg);

    /**
     * Persist a WARN log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param t An exception to log.
     */
    void warn(@NonNull String tag, @NonNull String msg, @NonNull Throwable t);

    /**
     * Persist a ERROR log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     */
    void error(@NonNull String tag, @NonNull String msg);

    /**
     * Persist a ERROR log message.
     * @param tag Used to identify the source of a log message.
     * @param msg The message you would like logged.
     * @param t An exception to log.
     */
    void error(@NonNull String tag, @NonNull String msg, @NonNull Throwable t);
}
+5 −0
Original line number Diff line number Diff line
@@ -392,4 +392,9 @@
    <integer name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis">60000</integer>
    <java-symbol type="integer" name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis" />

    <!-- Boolean indicating whether to enable persistent logging via DropBoxManager.
     Used in persisting SOS/emergency related log messages.
     -->
    <bool name="config_dropboxmanager_persistent_logging_enabled">false</bool>
    <java-symbol type="bool" name="config_dropboxmanager_persistent_logging_enabled" />
</resources>