YLog.java

// SPDX-License-Identifier: Apache-2.0
package com.shengzhiai.yugu.stcompat.internal;

import android.util.Log;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * SDK logger. Levels OFF, ERROR, WARN (default), INFO, DEBUG. Never receives secretKey, signature
 * or audio bytes; appKey is masked with {@link #maskKey(String)}.
 */
public final class YLog {
    public static final int OFF = 0;
    public static final int ERROR = 1;
    public static final int WARN = 2;
    public static final int INFO = 3;
    public static final int DEBUG = 4;

    public static final String TAG = "YuguStCompat";

    /** Optional extra sink (LogCat util writes SDK log lines to a file). */
    public interface Sink {
        void log(int level, String tag, String message, Throwable error);
    }

    private static volatile int level = WARN;
    private static volatile boolean levelPinned;
    private static volatile Sink sink;
    private static final Set<String> ONCE = Collections.synchronizedSet(new HashSet<String>());

    private YLog() {
    }

    public static int getLevel() {
        return level;
    }

    /** Level set by {@code YuguCompat.setLogLevel}; wins over EngineSetting log options. */
    public static void setLevelPinned(int newLevel) {
        level = clamp(newLevel);
        levelPinned = true;
    }

    /** Level derived from EngineSetting; ignored while a pinned level is set. */
    public static void setLevelFromSettings(int newLevel) {
        if (!levelPinned) {
            level = clamp(newLevel);
        }
    }

    /** EngineSetting switched SDK logs off: back to the default WARN unless a pinned level is set. */
    public static void clearLevelFromSettings() {
        if (!levelPinned) {
            level = WARN;
        }
    }

    public static void resetLevel() {
        level = WARN;
        levelPinned = false;
        ONCE.clear();
    }

    public static void setSink(Sink s) {
        sink = s;
    }

    public static Sink getSink() {
        return sink;
    }

    public static boolean isLoggable(int l) {
        return l != OFF && l <= level;
    }

    public static void e(String msg) {
        log(ERROR, msg, null);
    }

    public static void e(String msg, Throwable t) {
        log(ERROR, msg, t);
    }

    public static void w(String msg) {
        log(WARN, msg, null);
    }

    public static void w(String msg, Throwable t) {
        log(WARN, msg, t);
    }

    public static void i(String msg) {
        log(INFO, msg, null);
    }

    public static void d(String msg) {
        log(DEBUG, msg, null);
    }

    /** Logs a WARN line once per process for the given key. */
    public static void warnOnce(String key, String msg) {
        if (ONCE.add(key)) {
            log(WARN, msg, null);
        }
    }

    static void log(int l, String msg, Throwable t) {
        if (!isLoggable(l)) {
            return;
        }
        try {
            switch (l) {
                case ERROR:
                    Log.e(TAG, msg, t);
                    break;
                case WARN:
                    Log.w(TAG, msg, t);
                    break;
                case INFO:
                    Log.i(TAG, msg, t);
                    break;
                default:
                    Log.d(TAG, msg, t);
                    break;
            }
        } catch (RuntimeException ignored) {
            // android.util.Log is a stub on a plain JVM; logging must never break the SDK.
        }
        Sink s = sink;
        if (s != null) {
            try {
                s.log(l, TAG, msg, t);
            } catch (RuntimeException ignored) {
                // sink failures are not the caller's problem
            }
        }
    }

    /** First 4 characters of an appKey followed by {@code ***}. */
    public static String maskKey(String appKey) {
        if (appKey == null) {
            return "null";
        }
        return (appKey.length() <= 4 ? appKey : appKey.substring(0, 4)) + "***";
    }

    private static int clamp(int l) {
        return l < OFF ? OFF : (l > DEBUG ? DEBUG : l);
    }
}