Com o audit log plugin se tornando deprecated na versão 8.4 do Percona Server e a possibilidade de ser removido nas próximas versões, a alteração para o Audit Log Filter Component pode ser um processo para evitar novas dores de cabeça no futuro.
Neste blog, vou mostrar como instalar o Audit Log Filter Component e apresentar alguns exemplos práticos para demonstrar o funcionamento desse novo formato.
Como o Audit Log Filter funciona
O Audit Log Filter trabalha com regras JSON (filtros) e com associação por usuário.
Filtros são salvos na tabela mysql.audit_log_filter e os mapeamentos para cada usuário em mysql.audit_log_user.
Instalação e uso
O pacote traz um script SQL no diretório share/. Execute o load no banco mysql:
|
1 2 |
$ mysql -u root -p -D mysql < /usr/share/percona-server/audit_log_filter_linux_install.sql Enter password: |
Esse script cria as tabelas mysql.audit_log_filter e mysql.audit_log_user e funções (ex.: audit_log_filter_set_filter, audit_log_filter_set_user) e instala o audit_log component.
Confirme component ativo:
|
1 2 3 4 5 6 7 8 |
MySQL > SELECT * FROM mysql.component WHERE component_urn like '%audit%'; +--------------+--------------------+-----------------------------------+ | component_id | component_group_id | component_urn | +--------------+--------------------+-----------------------------------+ | 2 | 2 | file://component_audit_log_filter | +--------------+--------------------+-----------------------------------+ 1 row in set (0.00 sec) |
Instalado. Porém, você ainda não está salvando nenhuma informação. Para salvar as informações, precisamos criar os filtros.
Exemplo 1: logar tudo
Vamos iniciar os exemplos logando tudo, para isso vamos criar o filtro log_tudo que ativa o log para qualquer evento:
|
1 2 3 4 5 6 7 8 |
MySQL > SELECT audit_log_filter_set_filter( 'log_tudo', '{ "filter": { "log": true } }' ); |
Depois de criar o filtro, associe-o a todos os usuários usando o wildcard %:
|
1 |
MySQL > SELECT audit_log_filter_set_user('%', 'log_tudo'); |
Note que no primeiro comando do filter, utilizamos o nome log_tudo para o filtro e esse valor para associá-lo ao usuário.
Realizando um select qualquer, temos a seguinte saída:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
{ "timestamp": "2026-05-05 20:13:15", "id": 25, "class": "command", "event": "command_start", "connection_id": 16, "command_data": { "name": "command_start", "status": 0, "command": "Query"} }, { "timestamp": "2026-05-05 20:13:15", "id": 26, "class": "parse", "event": "query_rewritten", "connection_id": 16, "parse_data": { "flags": 0, "query": "select * from test.joinit", "rewritten_query": ""} }, { "timestamp": "2026-05-05 20:13:15", "id": 27, "class": "parse", "event": "prepared_statement", "connection_id": 16, "parse_data": { "flags": 0, "query": "select * from test.joinit", "rewritten_query": ""} }, { "timestamp": "2026-05-05 20:13:15", "id": 28, "class": "general", "event": "log", "connection_id": 16, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-05 20:13:15", "id": 29, "class": "query", "event": "query_start", "connection_id": 16, "query_data": { "query": "select * from test.joinit", "status": 0, "sql_command": "select"} }, { "timestamp": "2026-05-05 20:13:15", "id": 30, "class": "table_access", "event": "read", "connection_id": 16, "table_access_data": { "db": "test", "table": "joinit"} }, { "timestamp": "2026-05-05 20:13:15", "id": 31, "class": "query", "event": "query_status_end", "connection_id": 16, "query_data": { "query": "select * from test.joinit", "status": 0, "sql_command": "select"} }, { "timestamp": "2026-05-05 20:13:15", "id": 32, "class": "general", "event": "result", "connection_id": 16, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-05 20:13:15", "id": 33, "class": "general", "event": "status", "connection_id": 16, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-05 20:13:15", "id": 34, "class": "command", "event": "command_end", "connection_id": 16, "command_data": { "name": "command_end", "status": 0, "command": "Query"} } |
Antes do exemplo 2, vamos limpar esse filtro criado no exemplo 1:
|
1 |
MySQL > SELECT audit_log_filter_remove_filter('log_tudo'); |
Exemplo 2: logar somente comandos SELECT
Para o objetivo de logar os SELECTS, podemos usar o seguinte filtro no campo query_data.sql_command com valor "select", e podemos utilizar o exemplo acima pra criar o filtro que gostaríamos de receber no audit:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
MySQL > SELECT audit_log_filter_set_filter( 'log_selects', '{ "filter": { "log": false, "class": [ { "name": "query", "query_data.sql_command": "select" }, { "name": "general", "event": [ { "name": "log" } ] } ] } }' ); |
Para criar o filtro acima, retirei os nomes das classes e dos eventos do output exibido anteriormente. Abaixo, vou deixar mais claro de qual local:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{ "timestamp": "2026-05-05 20:13:15", "id": 28, "class": "general", #Classe para salvar o usuário e conexão "event": "log", #Classe para salvar o usuário e conexão "connection_id": 16, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 }] }, { "timestamp": "2026-05-05 20:13:15", "id": 29, "class": "query", #Classe para salvar o a query executada "event": "query_start", "connection_id": 16, "query_data": { "query": "select * from test.joinit", "status": 0, "sql_command": "select"} #Campo utilizado para validar o tipo de query executada }, |
Agora que temos o filtro criado, associe aos usuários novamente:
|
1 |
MySQL > SELECT audit_log_filter_set_user('%', 'log_selects'); |
Teste rápido:
|
1 2 3 |
MySQL > SELECT * FROM mysql.user; [...] 6 rows in set (0.00 sec) |
Checando as informações contidas no arquivo audit.log com este novo filtro:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
{ "timestamp": "2026-05-05 21:05:59", "id": 97, "class": "general", "event": "log", "connection_id": 16, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-05 21:05:59", "id": 98, "class": "query", "event": "query_start", "connection_id": 16, "query_data": { "query": "SELECT * FROM mysql.user", "status": 0, "sql_command": "select"} }, { "timestamp": "2026-05-05 21:05:59", "id": 99, "class": "query", "event": "query_status_end", "connection_id": 16, "query_data": { "query": "SELECT * FROM mysql.user", "status": 0, "sql_command": "select"} } |
Como é possível ver acima, com o segundo filtro, apenas as informações dos SELECTs e da Connection foram escritas no arquivo de audit; as demais foram filtradas com base no JSON criado.
Exemplo 3: Logar comandos de DDL
Novamente utilizando o exemplo 1 onde loga tudo no audit, vamos utilizá-lo para criar um log de comandos de DDL:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "timestamp": "2026-05-06 21:31:14", "id": 223, "class": "query", "event": "query_status_end", "connection_id": 334, "query_data": { "query": "drop table test.joinit", "status": 1051, "sql_command": "drop_table"} #Filtro para o drop table }, [...] { "timestamp": "2026-05-06 21:31:44", "id": 231, "class": "query", #Filtro de nome de classe "event": "query_status_end", #Filtro do nome do evento "connection_id": 334, "query_data": { "query": "CREATE TABLE `joinit` (\n `i` int(11) NOT NULL AUTO_INCREMENT,\n `s` varchar(64) DEFAULT NULL,\n `t` time NOT NULL,\n `g` int(11) NOT NULL,\n PRIMARY KEY (`i`)\n) ENGINE=InnoDB DEFAULT CHARSET=latin1", "status": 0, "sql_command": "create_table"} #Filtro para o create table }, |
Com as informações acima, podemos agora criar um filtro que apenas cheque os campos esperados:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
MySQL > SELECT audit_log_filter_set_filter( 'log_ddl', '{ "filter": { "log": false, "class": [ { "name": "general", "event": [ { "name": "log" } ] }, { "name": "query", "event": { "name": "status_end", "log": { "or": [ {"field": {"name": "sql_command_id", "value": "create_table"}}, {"field": {"name": "sql_command_id", "value": "drop_table"}} ] } } } ] } }' ); MySQL > SELECT audit_log_filter_set_user('%', 'log_ddl'); |
Após utilizamos os valores filtrados,
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
{ "timestamp": "2026-05-06 22:04:06", "id": 77, "class": "general", "event": "log", "connection_id": 358, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-06 22:04:06", "id": 78, "class": "query", "event": "query_status_end", "connection_id": 358, "query_data": { "query": "CREATE TABLE `joinit` (\n `i` int(11) NOT NULL AUTO_INCREMENT,\n `s` varchar(64) DEFAULT NULL,\n `t` time NOT NULL,\n `g` int(11) NOT NULL,\n PRIMARY KEY (`i`)\n) ENGINE=InnoDB DEFAULT CHARSET=latin1", "status": 0, "sql_command": "create_table"} }, #Esse valor abaixo é capturado por conta de uma execução de um insert, infelizmente como essa informação é capturada para termos informação de conexão, ela aparece para outras execuções também { "timestamp": "2026-05-06 22:04:11", "id": 79, "class": "general", "event": "log", "connection_id": 358, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-06 22:04:24", "id": 80, "class": "general", "event": "log", "connection_id": 358, "account": { "user": "root[root] @ localhost []", "host": "localhost" }, "login": { "user": "root[root] @ localhost []", "ip": "", "proxy": "" }, "general_data": { "status": 0 } }, { "timestamp": "2026-05-06 22:04:24", "id": 81, "class": "query", "event": "query_status_end", "connection_id": 358, "query_data": { "query": "DROP TABLE joinit", "status": 0, "sql_command": "drop_table"} } |
Com base nos outputs recebidos anteriormente, conseguimos criar o filtro, correlacionando os valores que queremos salvar com cada key. Por exemplo, para os valores de sql_command podemos utilizar a seguinte lista:
https://github.com/percona/percona-server/blob/Percona-Server-8.4.8-8/sql/command_mapping.cc#L51
E adicionar mais fields no filtro acima. Exemplo:
|
1 2 3 4 5 6 7 8 9 |
[...] "log": { "or": [ {"field": {"name": "sql_command_id", "value": "create_table"}}, {"field": {"name": "sql_command_id", "value": "drop_table"}}, {"field": {"name": "sql_command_id", "value": "alter_table"}}, {"field": {"name": "sql_command_id", "value": "drop_user"}} ] [...] |
Recomendação: verifique todas as informações que você gostaria de ter após a execução de um audit completo. Assim, você terá os campos e os valores de forma mais prática para futuros filtros.
É possível criar diferentes regras para serem aplicadas a diferentes usuários, o que proporciona maior flexibilidade para salvar apenas o que é necessário, sem ocupar uma grande quantidade de espaço em disco.
Conclusão
Com o Audit Log Plugin se tornando deprecated, o Audit Log Filter Component será o melhor caminho para auditoria no Percona Server e no MySQL, visto que ambos seguem o mesmo padrão.
Como vimos, o Audit Log Filter Component permite migrar do modelo de logar tudo para um modelo muito mais direcionado, criando filtros personalizados por usuário ou por comando. Com isso, é possível ajustar a auditoria para registrar apenas os eventos realmente importantes, reduzindo o volume de logs e tornando a análise de auditoria muito mais objetiva e eficiente.
Referências
- MySQL 8.4 Reference Manual — Installing or Uninstalling MySQL Enterprise Audit: https://dev.mysql.com/doc/refman/8.4/en/audit-log-installation.html
- Percona Server for MySQL 8.4 — Audit Log Filter overview: https://docs.percona.com/percona-server/8.4/audit-log-filter-overview.html
- Percona Server for MySQL 8.4 — Audit log plugin (deprecated): https://docs.percona.com/percona-server/8.4/audit-log-plugin.html





