Pull to refresh

Comments 28

Что бросилось в глаза при беглом взгляде на github.com/zserge/log/blob/master/library/src/main/java/trikita/log/Log.java

1. Использование reflection медленно в android. У вас в каждом выводе в лог используется reflection.
2. Не thread-safe, т.к. используется статических контейнер: private static Map<String, String> tags = new HashMap<>();
3. Да и другие статические не final поля не дают использовать библиотеку в разных потоках

Спасибо что заглянули в исходник!

1. Рефлексия — это плата за удобство использования. Timber кстати делает так же, использует рефлексию для поиска тега. К тому же считается что компиляторы неплохо оптимизируют рефлексию в наши дни. Можно конечно проделать бенчмарки чтобы доказать обратное. Но логирование даже без рефлексии считается небыстрым — там JNI вызовы, затем syscall (запись в лог-устройство).

2/3. А вот по поводу тред-сейфности вы дело говорите. Это точно надо бы исправить. Если хотите — можете багу открыть на гитхабе, чтобы не забылось.
4. Получение имени вызываемого класса через создание исключение (new Throwable().getStackTrace();) медленно.
5. Конструктор должен быть приватным. (насколько я понял, экземпляр этого класса из вне нельзя создать).
6. Поля V, D, I, и т.д., наверное, стоит сделать в виде enum.

P.S. Игра — найди 10 ошибок?
Отнюдь. Не всё и не всегда замечаешь сам и сразу, так что за замечания — спасибо.

4. А существует ли более быстрый способ узнать имя вызывающего класса?
5. Упс. И ведь хотел сделать «utility class», а конструктор потерял где-то.
6. Наверняка enum идеологичнее, но сейчас я могу написать Log.level(Log.D), а с энамом будет чуть длинее. Разве только сам Log попробовать сделать энамом. А кстати в android.util.Log уровни сделаны константами, а не энамом.
4. А существует ли более быстрый способ узнать имя вызывающего класса?

К сожалению, я его не знаю. По моему, в runtime этого нельзя быстро сделать.

А кстати в android.util.Log уровни сделаны константами, а не энамом.

Интересно, не знал. Сейчас посмотрел — действительно, числовые константы. Вообще в JUL (java.util.logging) Level тоже не enum, но используется как enum — final static поля с экземплярами этого класса на которые потом ссылаются.

P.S. Я вообще не придираюсь — просто интересно сделать code review. :)
Да я к критике нормально отношусь — чем больше потенциальных ошибок я узнаю сейчас, тем меньше времени потрачу если бы сам наступал на эти грабли в будушем.

Кстати, внизу привел замеры скорости моего логгера и стандартного. Рефлексия конечно медленнее, но вполне приемлемо для логгера. Мы же как правило пишем логи несколько раз в секудну, не чаще.

А к энамам у меня какая-то нелюбовь в джаве. Они там как-то сбоку-припеку прикручены и все время вызывают настороженность (ни отнаследовать, ни расширить). Хотя в том же C я энамы люблю и регулярно использую.
4. Я пользовался Thread.currentThread().getStackTrace(). Вряд ли это быстрее, но выглядит красивее.
Разница-то скорее всего есть, но она минимальна — стоимость создания одного небольшого объекта намного дешевле разворачивания стека (которое выполняется в обоих случаях).
По поводу
1. Рефлексия — это плата за удобство использования.
В целом согласен — использовать удобно. Но нельзя ли в Android использовать compile-time аспекты? Может что то вроде AspectJ CTW? Может с этим получится сделать быстрое получение имени метода и класса? Т.е. при компиляции аспектов получать имя метод и класса у вызывающего кода и дальше его использовать. Конечно, будет сложнее чем сейчас — нужно будет компиляцию настраивать в gradle, но зато perfomance будет лучше.
И в .class и в .dex видно, что компилятор ничего не оптимизировал:
...
public void print(int i, String s, String s1)
    {
        try
        {
            if(loaded)
                mLogMethods[i].invoke(null, new Object[] {
                    s, s1
                });
            return;
        }
...


Или имелись ввиду hotspot, dalvik и ART?
Да, я про конечное время работы, пользователю-то важно не тормозит ли его приложение из-за корявого логгера.

Вообщем, я померял. Moto G 1st gen, середнячок, но не флагман. android.util.Log в среднем занимает 7-10 микросекунд на сообщение, trikita.log.Log в среднем занимает 40-50 микросекунд.

Признаю, рефлексия (ожидаемо) медленнее, практически на порядок, но в целом можно успеть записать 300 логов пока сменится один кадр при 60 fps. Так что я бы не сказал что потеря скорости логгера здесь критична.
Ещё есть библиотека github.com/orhanobut/logger, если кому интересно.
Да, неплохой и весьма популярный логгер. Кстати, упомянут в статье.
Меня лично в нем не устроило: отсутствие логгирования через запятую (только форматные строки), странноватое API (я понимаю, что это игра слов на тему лесорубов, но глазу непривычно), нет поддержки просто JVM (без android.util.Log).

(мимо, это должно было быть в ответ Suvitruf)
Кхм… стесняюсь спросить… а почему нельзя было использовать slf4j?
У него API совсем далекое от android.util.Log (а значит сложнее мигрировать). Да и как всегда, отсутствует вывод объектов через запятую без форматной строки (как console.log в js или log.Println в Go). Логеров много, каджый чем-то хорош. На вкус и цвет. Когда я писал свой — не было логгера с совместимым API и не было логгера для varargs через запятую без форматной строки. Теперь есть.
А в чём фишка поля (field) tag? Я так по коду понял, что если это поле есть, то при выводе лога оно печатается. Так? Ну и не могли бы Вы use case показать применимости тега? А то я с таким первый раз встречаюсь.
Смысл в легкой миграции. Если люди используют android.util.Log, то зачастую в их классах есть атрибут TAG (или tag). Если они перейдут на trikita.log.Log — то ожидаемо что теги по прежнему будут браться из атрибута TAG.
Было бы очень круто добавить функционал по управлению уровнем логирования отдельно для каждого тэга (или еще какой-то сущности). Обычно в очень большом проекте логов очень много, но часто хочется включить только какие-то определенные, например – посмотреть на одновременную работу уровня сети и уровня БД. Да, можно, конечно, фильтровать по набору тагов, но на мой взгляд в Logcat этот функционал реализован неудобно.
В принципе, это технически можно провернуть с помощью кастомного принтера (туда приходит уровень лога, тэг и сообщение). Но лично я все равно фильтрую logcat'ом (точнее, надстройкой над ним).
Зачем нужен был ещё один велосипед? Только из-за названия класса? В продакшине логи лучше отправлять в аналитику, не нашёл такого функционала у вас.
Можно сделать собственный принтер логов. Например,

Log.usePrinter(new Printer() {
  public void print(int level, String tag, String msg) {
    Crashlytics.log(level, tag, msg);
  }
}, true).usePrinter(System.ANDROID, false);
Log.d("Hello"); // выведется в крэшлитикс
Небольшой оффтопик: а что вы имели в виду, когда писали
Там было все что только можно было запихнуть — и retrolambda/java8

Как retrolambda подключить к Андроид-проекту я в курсе, а вот про Java 8 не слышал. Там же вроде только 1.7 доступна, причём try-with-resources — начиная с KitKat. Или я не прав?
Не-не, все верно, я про то что компилилось все jdk8, затем с помощью ретролямбды в байткод 7й джавы превращалось. Да, увы всех фич java8 не видать, разве только гугл свои jack/jill доведут до ума. А, ну правда еще стримы бэкпортировали — github.com/aNNiMON/Lightweight-Stream-API

(мимо, да что ж такое, это в ответ artemgapchenko )
Логгер, которым пользуюсь я: org.droidparts.util.L.
Вкратце фишки:
— тег не нужен. В debug версии в качестве тега название класса, метода и номер строки, откуда произведено логирование. В release — название класса.
— логлевел можно настроить через AndroidManifest.xml (например, вообще выключить логирование для релиза).
— можно логироавть любой объект, поддерживается стандартный синтаксис String.format.
Sign up to leave a comment.

Articles