Две сущности: Event, Category.
Category — справочник, который задаёт категорию аудит-сообщения со строковым ключом в формате level1.level2...levelN — зависит от глубины структуры, которая обычно строится по детализации предметной области. Не что иное как хитрый способ отфильтровать дочерние подкатегории, не прибегая к иерархическим запросам — через LIKE ’parentId.%’, ну и суррогат обхода всего дерева через простой ORDER BY id. Тут же хранится человеческое название категории, severity level (тот самый INFO, WARN, etc), ну и самое интересное — атрибут pattern, о котором чуть позже.
Event — собственно, событие, эквивалент одной записи в лог-файле. Содержит время регистрации, обязательную ссылку на сущность Category, привязку к каким-то прочим сущностям, связанным с событием — например пользователя, породившего событие. И самое главное — произвольный набор текстовых атрибутов «ключ/значение». В реляционной БД это будет третья таблица, для правильного ORM это выглядит как обычный атрибут Map[String,String], ну а для NoSQL — это вообще родная концепция. Там хранятся параметры конкретного события, всё то, что когда-нибудь может понадобиться для анализа.
Таким образом мы обеспечили машинную читаемость событий, что же касается человеко-читаемости, то она реализуется атрибутом Category.pattern, который представляет из себя выражение с синтаксисом какого-нибудь простого движка-шаблонизатора, который не сложно реализовать и самостоятельно, где, помимо статических фрагментов текста, могут присутствовать ссылки на атрибуты события. Например: «User {user.login} logged in from {ip:unknown IP}». Генерация финального текстового выражения происходит динамически уже на уровне поближе к пользовательскому интерфейсу. Это даёт возможность впоследствии изменить шаблон сообщения, например, добавив новые параметры или ввести возможность многоязыковых сообщений.
Теперь о некоторых полезных расширениях, построенных на этой концепции. Можем определить интерфейс с одним методом, например такой (дальше пойдёт код на Scala, но это не принципиально):
trait Auditable {
def auditAttributes: Map[String, Any]
}
class User extends Entity with Auditable {
// model entity definition...
override def auditAttributes = super.auditAttributes ++ Map(
«login» → login,
«name.full» → s"$firstName $lastName«,
«roles» → roles sortBy (_.id) map (_.name)
)
}
теперь можем определить метод для логгирования как:
class AuditService {
def log(category: String, attributes: (String, Any)*) = ???
}
и реализовать в нём раскрытие параметров через интерфейс Auditable.
Таким образом, вызов
auditService.log("security.login«, «user» → user, «ip» → ip)
сгенерирует событие с примерно таким набором атрибутов:
user.id=123
user.login=basil
user.name.full=Basil Pupkin
user.name.roles=Administrator Manager
user.name.roles.0=Administrator
user.name.roles.1=Manager
ip=1.2.3.4
что по вышеприведённому шаблону построит текстовое сообщение: «User basil logged in from 1.2.3.4»
Ну и, разумеется, для машинного поиска у нас есть все нужные данные в сыром виде.
Дальше, реализовав в модельных классах достаточно подробно интерфейс Auditable, можем без особого труда сделать элегантное логгирование изменений данных. Например, с помощью метода с такой сигнатурой:
def diff[T <: Auditable, R](category: String, obj: T)(action: ⇒ R): R = ???
что откроет возможность такого рода трюкам:
val user = load[User](userId)
auditService.diff("user.change«, user) {
user.firstName = request.firstName
user.lastName = request.lastName
}
Метод diff на входе получит состояние объекта до изменений, сериализует его в плоские строковые атрибуты через интерфейс Auditable, затем выполнит тело выражения в фигурных скобках, и повторно сериализует атрибуты. Сравнив две коллекции легко найти изменившиеся значения. В случае, если таковые есть, можно залоггировать событие с указанием конкретных изменившихся атрибутов, например, в формате «было > стало».
Ну вот, вкратце, суть идеи. Буду рад, если кому-нибудь ещё пригодится.
Почему-то все зациклены на текстовых лог-файлах, игнорируя возможность генерации более структурированных и машинно-читаемых событий в БД. Например, у нас на многих проектах используется собственный движок для такого рода аудит-логгинга с разными дополнениями вроде автоматической регистрации изменений в модели данных. Если нужно, могу поделиться основными идеями.
То есть, на маршруте Киев-Амстердам-Цюрих мне должны были в Амстердаме поставить штамп о въезде?
Вот тут всё подробно изложено: blog.kupibilet.ru/transit-visa
Если внутри Шенгена всего одна пересадка и в аэропорту есть транзитная зона, то таможенный контроль не производится.
В 2009. По вашей логике один из штампов в паспорте должен был быть голландский, но вот он передо мной — оба штампа швейцарские.
Киев-Амстердам-Цюрих рейсами KLM
Вы уверены? Что-то не припомню чтоб транзитом через Шенген в Шенген я вообще таможенный контроль проходил.
Вы просто не в теме. Приватбанк заблокировал все текущие счета ФОПов, открытые в крымских филиалах. Доступа к средствам нет до сих пор ни у кого, несмотря на перерегистрацию на материке и открытие новых счетов в других отделениях. Перерегистрироваться крымчанам стало довольно просто относительно недавно. В течение полугода до этого обязательным условием была местная прописка. На все претензии банк тупо морозится. НБУ не вмешивается. Милиция заявления не принимает. Иски и физических, и юридических лиц украинскими судами отклоняются со ссылкой на форс-мажорные обстоятельства в Крыму. Так что мой совет, как непосредственно пострадавшего, немедленно выводите все свои деньги из донецких и луганских отделений Приватбанка.
Отнюдь. Ни одна из этих фич не является функциональной. Все они вполне могли бы присутствовать в чисто императивном языке со строгой типизацией, таком как Java или C#.
Годная статья по type classes была здесь: danielwestheide.com/...pe-classes.html ; сейчас почему-то только через кэш: webcache.googleusercontent.com/...lient=firefox-a
Scala позволяет избежать многих проблем с наследованием при помощи traits, type classes, abstract types, stackable traits, structural types, implicit conversions, views — если и не уникальные, то довольно редкие фичи для языков программирования. Практикую все из них, прямому наследованию предпочитаю компонование свойств в виде подмешивания из других trait’ов (mixins) или создание ускоспециализированных type classes.
Интересно как себя будут чувствовать офшорные девелоперские компании в Крыму, сотрудничавшие с американцами в свете только что принятых санкций: lenta.ru/...0/usasanctions