1. Предпосылки
Моя компания недавно потребовала десенсибилизации конфиденциальных данных во всем мире, вероятно, из-за утечки данных FaceBook.
Когда дело доходит до десенсибилизации, десенсибилизация обычно требуется там, где выводятся данные, и обычно есть три места, где наши данные выводятся:
- Десенсибилизация возвращаемого значения интерфейса
- журнал десенсибилизации
- Десенсибилизация базы данных
Здесь мы в основном говорим о том, как десенсибилизировать журналы.Для кода существует два типа конфиденциальных данных для печати журнала:
- Конфиденциальные данные в параметрах метода
LOGGER.info("person mobile:{}", mobile);
Для такого рода предложений напишите Util для прямой десенсибилизации, потому что имя параметра мобильного телефона не может быть получено в коде.В то время я думал об использовании обычного сопоставления для переданных параметров, что было бы слишком неэффективно и вызывало бы каждый log на сбой.Обычное сопоставление крайне неэффективно, и если встречается строка, совпадающая с номером телефона, но не являющаяся конфиденциальной информацией, она также десенсибилизируется.
LOGGER.info("person mobile:{}", DesensitizationUtil.mobileDesensitiza(mobile));
2. Конфиденциальные данные находятся в объекте параметра
Person person = new Person();
person.setMobile(mobile);
LOGGER.info("person :{}", person);
Для нашего бизнеса наиболее распространенным является приведенный выше лог.Чтобы завершить весь параметр, первый метод должен вынести параметр, а второй метод должен передать только один параметр, а затем распечатать лог через toString. Есть два варианта такой десенсибилизации.
- Чтобы изменить метод toString, существует три способа изменить метод toString:
- Изменение кода непосредственно в toString очень хлопотно и неэффективно. Вам нужно изменить каждый класс, чтобы он был десенсибилизирован, или написать плагин идеи для автоматического изменения toString(). Недостатком является то, что все компиляторы должны открывать плагины, которые недостаточно универсальны.
- Измените абстрактное синтаксическое дерево и измените метод toString() во время компиляции, точно так же, как Lombok. Это было исследовано ранее, и его сложно разрабатывать. Позже его можно будет обновить, как его написать.
- При загрузке путем реализации интерфейса Instrumentation + asm библиотека модифицируется байткод файла класса, но есть проблемное место, которое нужно добавить в jvm с параметром запуска -javaagent:agentjarпуть, это было реализовано, но после реализации оказалось, что оно недостаточно универсально.
- Видно, что модифицировать три вышеуказанных метода toString() проблематично. Мы можем изменить свое мышление и генерировать информацию журнала, не используя toString(). В следующих разделах объясняется, как это сделать.
2. Схема
Сначала нам нужно знать, что происходит, когда мы используем LOGGER.info? Как показано на рисунке ниже, я перечисляю здесь асинхронную ситуацию (асинхронность используется в наших проектах, и эффективность синхронизации слишком низкая).
У log4j слишком много мест, где можно предоставить нам расширения. Вы можете настраивать его так долго, как вам это нужно. Например, собственный объединенный журнал xmdt Meituan Dianping и автономный журнал тревог - все Appenders, реализованные сами по себе. Унифицированный журнал также поддерживает LogEvent в упаковке.
Мы также можем использовать расширяемость, предоставляемую Log4j2, для настройки собственных нужд.
2.1 Настройка преобразования PatternLayout
То есть измените шаг 8 на рисунке выше. Переписав Convert и добавив логику фильтрации.
преимущество:
Этот способ идеален, он не повлияет на производительность нашего лога, потому что логика фильтрации находится в PatternLayout.
недостаток:
Но я очень смущен в этом месте.Я могу получить только сгенерированную строку.Я могу использовать только глупый способ сопоставления слово за словом, а затем десенсибилизировать данные после изменения слова, что слишком сложно. Я думал о том, какой алгоритм использовать для оптимизации (например, те системы комментариев, которые фильтруют деликатные слова в статьях с десятками тысяч слов), но цена слишком высока, поэтомусдаться.
2.2 Настройка глобального фильтра
Когда я думал о первом методе, я на самом деле столкнулся с узким местом в это время. В то время я не полностью проанализировал ссылку Log4j2. Позже я подумал, что, возможно, смогу найти больше идей по панорамной ссылке Log4j2. выше.
Почему решение в 2.1 выше невозможно? В основном я могу получить только сгенерированную строку. В это время я подумал, что было бы здорово, если бы я мог изменить метод генерации String.Журнал на самом деле просто строка.Неважно, откуда взялась строка.
В это время я подумал о json, который также является строкой, форматом для нашего обмена данными. При генерации Json фильтруйте и уменьшайте чувствительность значений, которые нам нужно преобразовать для достижения нашей цели.
Конечно, преобразование методов Json и toString() может иметь большую разницу в эффективности. В настоящее время можно пожертвовать только fastjson. Fastjson использует технологию байт-кода asm, чтобы избавиться от сниженной эффективности отражения. Следующие тесты производительности Он также имеет было заявлено, что влияние на эффективность в основном незначительно.
Таким образом, мы требуем два фильтра: фильтр для десенсибилизации - это log4j2 журналов, когда фильтр используется для преобразования fastjson json, представляют собой определенные поля для обработки.
преимущество:
Изменения минимальны, и вам нужно только добавить этот фильтр в файл конфигурации Log4j.xml, чтобы он вступил в силу глобально, и вы можете его использовать.
недостаток:
1. Поскольку действует глобально, каждый лог неизбежно будет конвертироваться из предыдущего toString в json, что может быть неприменимо к некоторым сервисам, стремящимся к экстремальной производительности (например, даже более 1 мс недопустимо).
2. Видно, что мы на первом шаге, а за первым шагом следует встроенный фильтр уровня, потому что мы иногда динамически подстраиваем уровень лога, что вызовет у нас даже если это не текущий уровень вывода , он также будет Это немного потеря для переключения.
После оптимизации второго пункта я также заранее проделал работу фильтра уровня, и напрямую отклонил уровень, если уровня не хватает.
Пример кода выглядит следующим образом:
@Plugin(name = "CrmSensitiveFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
public class CrmSensitiveFilter extends AbstractFilter {
private static final long serialVersionUID = 1L;
private final boolean enabled;
private CrmSensitiveFilter(final boolean enabled, final Result onMatch, final Result onMismatch) {
super(onMatch, onMismatch);
//线上线下开关
this.enabled = enabled;
}
@Override
public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
final Throwable t) {
return filter(logger, level, marker, null, msg);
}
@Override
public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) {
if (this.enabled == false) {
return onMatch;
}
if (level == null || logger.getLevel().intLevel() < level.intLevel()) {
return onMismatch;
}
if (params == null || params.length <= 0) {
return super.filter(logger, level, marker, msg, params);
}
for (int i = 0; i < params.length; i++) {
params[i] = deepToString(params[i]);
}
return onMatch;
}
@PluginFactory
public static CrmSensitiveFilter createFilter(@PluginAttribute("enabled") final Boolean enabled,
@PluginAttribute("onMatch") final Result match,
@PluginAttribute("onMismatch") final Result mismatch) throws IllegalArgumentException,
IllegalAccessException {
return new CrmSensitiveFilter(enabled, match, mismatch);
}
}
2.3 Переписать фабрику сообщений
Недостатком вышеуказанного глобального фильтра является то, что его нельзя настроить.В настоящее время я сосредоточусь на третьем шаге, создании содержимого журнала и выводе сообщения.
Переопределяя MessageFactory, мы можем сгенерировать собственное сообщение и указать на уровне кода, использует ли наш LoggerMannger нашу собственную MessageFactory или фабрику по умолчанию, которой мы можем управлять сами.
Конечно, основная идея сообщения, которое мы здесь генерируем, остается той же, что и у фильтра значений fastjson.
преимущество:
LOGGER может быть настроен, не является глобальным.
недостаток:
Ограничено Log4j2, другие платформы ведения журналов, такие как LogBack, неприменимы.
Часть кода приведена ниже:
public class DesensitizedMessageFactory extends AbstractMessageFactory {
private static final long serialVersionUID = 1L;
/**
* Instance of DesensitizedMessageFactory.
*/
public static final DesensitizedMessageFactory INSTANCE = new DesensitizedMessageFactory();
/**
* @param message The message pattern.
* @param params The message parameters.
* @return The Message.
*
* @see MessageFactory#newMessage(String, Object...)
*/
@Override
public Message newMessage(String message, Object... params) {
return new DesensitizedMessage(message, params);
}
/**
*
* @param message
* @return
*/
@Override
public Message newMessage(Object message) {
return new ObjectMessage(DesensitizedMessage.deepToString(message));
}
}
3. Используйте
До бизнес-проекта нашей команды log4j использовал версию 2.6.Он все время использовал фильтр.Внезапно его обновили до 2.7.Внезапно десенсибилизация не сработала.В то время мы изучали исходный код и обнаружили, что фильтр были некоторые изменения.Есть проблема, когда параметр журнала меньше или равен 2.
Вам нужно выбрать наиболее подходящий для вашего бизнес-сценария в соответствии с вашим бизнес-сценарием:
Если версия log4j меньше 2.6, используйте фильтр, а если больше 2.6 (конечно, можно использовать и если не больше 2.6), используйте MessageFactory
3.1 настройка фильтра (выберите один из двух)
Найдите Log4j.xml (каждой среде соответствует свой ha)
Ниже самого внешнего узла, то есть внутри запишите следующую конфигурацию, enable используется для онлайн- и оффлайн-переключения, true — допустимо, а false — недопустимо.
3.2 Конфигурация MessageFactory (выберите один из двух)
Создайте файл: log4j2.component.properties
Введите: log4j2.messageFactory=log.message.DesensitizedMessageFactory
4. Контрольные показатели производительности:
Тесты сосредоточены на том, насколько эффективна печать журналов.
аппаратное обеспечение:
4核,8G
Операционная система:
linux
JRE:
v1.8.0_101,初始堆大小4G
Стратегия разогрева:
测试开始前,全局预热,执行全部测试若干次,判断运行时间稳定后停止,确保所需class全部加载完成每个测试开始前,独立预热,重复执行该测试64次,确保JIT编译器充分优化完代码。
Стратегия исполнения:
循环执行,初始次数200,以200的步长递增,递增至1000为止。每次执行10次,去掉一个最高,去掉一个最低,取平均值。
Результаты теста:
Из приведенных выше результатов видно, что скорость роста в основном стабильна.
Время десенсибилизации для приведенных выше результатов примерно в 1,5 раза больше, чем время без десенсибилизации.
В среднем один журнал создается за 0,1255 мс для недесенсибилизированных, а один журнал генерируется за 0,18825 мс для десенсибилизированных, разница между ними составляет около 0,06 мс.
По оценкам, весь наш запрос имеет максимум 10-20 отпечатков журнала, и весь запрос повлияет на среднее время около 0,6 мс-1,2 мс.Я думаю, что это время можно игнорировать во всем запросе.
Таким образом, производительность этого режима по-прежнему относительно высока, и его можно применять в производственной среде.
Для получения дополнительной информации, пожалуйста, отсканируйте мой технический общедоступный аккаунт.
Чтобы облегчить всем обучение и общение, была создана qq java back-end коммуникационная группа: 837321192, которая содержит мою коллекцию обучающих видео 100G (охватывающих интервью, архитектуру и т. д.), а также множество материалов для интервью, Вы можете присоединиться и общаться вместе.