Skip to content

Commit 4afe778

Browse files
committed
feat enable mask log by maskLog for parameter locally and maskLogResultColumns for result globally
1 parent c770bd5 commit 4afe778

File tree

23 files changed

+594
-39
lines changed

23 files changed

+594
-39
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
<dependency>
197197
<groupId>org.postgresql</groupId>
198198
<artifactId>postgresql</artifactId>
199-
<version>42.7.6</version>
199+
<version>42.7.5</version>
200200
<scope>test</scope>
201201
</dependency>
202202
<dependency>

src/main/java/org/apache/ibatis/builder/ParameterMappingTokenHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ private ParameterMapping buildParameterMapping(String content) {
113113
builder.resultMapId(value);
114114
} else if ("jdbcTypeName".equals(name)) {
115115
builder.jdbcTypeName(value);
116+
} else if ("maskLog".equals(name)) {
117+
builder.maskLog(value);
116118
} else if ("expression".equals(name)) {
117119
throw new BuilderException("Expression based parameters are not supported yet");
118120
} else {

src/main/java/org/apache/ibatis/builder/xml/XMLConfigBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2024 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -118,6 +118,7 @@ private void parseConfiguration(XNode root) {
118118
Properties settings = settingsAsProperties(root.evalNode("settings"));
119119
loadCustomVfsImpl(settings);
120120
loadCustomLogImpl(settings);
121+
loadCustomMaskLogResultColumns(settings);
121122
typeAliasesElement(root.evalNode("typeAliases"));
122123
pluginsElement(root.evalNode("plugins"));
123124
objectFactoryElement(root.evalNode("objectFactory"));
@@ -170,6 +171,10 @@ private void loadCustomLogImpl(Properties props) {
170171
configuration.setLogImpl(logImpl);
171172
}
172173

174+
private void loadCustomMaskLogResultColumns(Properties props) {
175+
configuration.setMaskLogResultColumns(props.getProperty("maskLogResultColumns"));
176+
}
177+
173178
private void typeAliasesElement(XNode context) {
174179
if (context == null) {
175180
return;

src/main/java/org/apache/ibatis/executor/BaseExecutor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowB
355355
protected Connection getConnection(Log statementLog) throws SQLException {
356356
Connection connection = transaction.getConnection();
357357
if (statementLog.isDebugEnabled()) {
358-
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
358+
return ConnectionLogger.newInstance(connection, statementLog, queryStack,
359+
configuration.getMaskLogResultColumns());
359360
}
360361
return connection;
361362
}

src/main/java/org/apache/ibatis/logging/jdbc/BaseJdbcLogger.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2024 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,11 +42,13 @@ public abstract class BaseJdbcLogger {
4242

4343
protected static final Set<String> SET_METHODS;
4444
protected static final Set<String> EXECUTE_METHODS = new HashSet<>();
45+
protected static final Set<String> MASK_LOG_RESULT_COLUMNS = new HashSet<>();
4546

4647
private final Map<Object, Object> columnMap = new HashMap<>();
4748

4849
private final List<Object> columnNames = new ArrayList<>();
4950
private final List<Object> columnValues = new ArrayList<>();
51+
private final List<Boolean> columnMasks = new ArrayList<>();
5052

5153
protected final Log statementLog;
5254
protected final int queryStack;
@@ -55,12 +57,22 @@ public abstract class BaseJdbcLogger {
5557
* Default constructor
5658
*/
5759
public BaseJdbcLogger(Log log, int queryStack) {
60+
this(log, queryStack, null);
61+
}
62+
63+
/*
64+
* Default constructor
65+
*/
66+
public BaseJdbcLogger(Log log, int queryStack, Set<String> maskLogResultColumns) {
5867
this.statementLog = log;
5968
if (queryStack == 0) {
6069
this.queryStack = 1;
6170
} else {
6271
this.queryStack = queryStack;
6372
}
73+
if (maskLogResultColumns != null) {
74+
MASK_LOG_RESULT_COLUMNS.addAll(maskLogResultColumns);
75+
}
6476
}
6577

6678
static {
@@ -85,12 +97,17 @@ protected Object getColumn(Object key) {
8597
}
8698

8799
protected String getParameterValueString() {
88-
List<Object> typeList = new ArrayList<>(columnValues.size());
89-
for (Object value : columnValues) {
100+
int size = columnValues.size();
101+
boolean equal = size == columnMasks.size();
102+
List<Object> typeList = new ArrayList<>(size);
103+
for (int i = 0; i < size; ++i) {
104+
Object value = columnValues.get(i);
90105
if (value == null) {
91106
typeList.add("null");
92107
} else {
93-
typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
108+
String strVal = objectValueString(value);
109+
boolean mask = equal && columnMasks.get(i);
110+
typeList.add(mask ? mask(strVal) : strVal + "(" + value.getClass().getSimpleName() + ")");
94111
}
95112
}
96113
final String parameters = typeList.toString();
@@ -112,10 +129,15 @@ protected String getColumnString() {
112129
return columnNames.toString();
113130
}
114131

132+
public void addColumnMasks(Boolean maskLog) {
133+
this.columnMasks.add(maskLog);
134+
}
135+
115136
protected void clearColumnInfo() {
116137
columnMap.clear();
117138
columnNames.clear();
118139
columnValues.clear();
140+
columnMasks.clear();
119141
}
120142

121143
protected String removeExtraWhitespace(String original) {
@@ -142,6 +164,25 @@ protected void trace(String text, boolean input) {
142164
}
143165
}
144166

167+
protected String mask(String value) {
168+
if (value == null || value.isEmpty()) {
169+
return value;
170+
}
171+
int length = value.length();
172+
if (length == 1) {
173+
return value;
174+
} else if (length == 2) {
175+
return value.charAt(0) + "*";
176+
} else if (length == 3) {
177+
return value.charAt(0) + "*" + value.charAt(2);
178+
} else {
179+
int maskLen = length >> 1;
180+
int rawLen = length - maskLen;
181+
return value.substring(0, (rawLen & 1) == 0 ? rawLen >> 1 : (rawLen >> 1) + 1) + "*".repeat(maskLen)
182+
+ value.substring(length - (rawLen >> 1));
183+
}
184+
}
185+
145186
private String prefix(boolean isInput) {
146187
char[] buffer = new char[queryStack * 2 + 2];
147188
Arrays.fill(buffer, '=');

src/main/java/org/apache/ibatis/logging/jdbc/ConnectionLogger.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import java.sql.Connection;
2222
import java.sql.PreparedStatement;
2323
import java.sql.Statement;
24+
import java.util.Set;
2425

2526
import org.apache.ibatis.logging.Log;
2627
import org.apache.ibatis.reflection.ExceptionUtil;
@@ -36,7 +37,11 @@ public final class ConnectionLogger extends BaseJdbcLogger implements Invocation
3637
private final Connection connection;
3738

3839
private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
39-
super(statementLog, queryStack);
40+
this(conn, statementLog, queryStack, null);
41+
}
42+
43+
private ConnectionLogger(Connection conn, Log statementLog, int queryStack, Set<String> maskLogResultColumns) {
44+
super(statementLog, queryStack, maskLogResultColumns);
4045
this.connection = conn;
4146
}
4247

@@ -55,7 +60,7 @@ public Object invoke(Object proxy, Method method, Object[] params) throws Throwa
5560
}
5661
if ("createStatement".equals(method.getName())) {
5762
Statement stmt = (Statement) method.invoke(connection, params);
58-
return StatementLogger.newInstance(stmt, statementLog, queryStack);
63+
return StatementLogger.newInstance(stmt, statementLog, queryStack, MASK_LOG_RESULT_COLUMNS);
5964
}
6065
return method.invoke(connection, params);
6166
} catch (Throwable t) {
@@ -76,7 +81,26 @@ public Object invoke(Object proxy, Method method, Object[] params) throws Throwa
7681
* @return the connection with logging
7782
*/
7883
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
79-
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
84+
return newInstance(conn, statementLog, queryStack, null);
85+
}
86+
87+
/**
88+
* Creates a logging version of a connection.
89+
*
90+
* @param conn
91+
* the original connection
92+
* @param statementLog
93+
* the statement log
94+
* @param queryStack
95+
* the query stack
96+
* @param maskLogResultColumns
97+
* the result columns to be masked
98+
*
99+
* @return the connection with logging
100+
*/
101+
public static Connection newInstance(Connection conn, Log statementLog, int queryStack,
102+
Set<String> maskLogResultColumns) {
103+
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack, maskLogResultColumns);
80104
ClassLoader cl = Connection.class.getClassLoader();
81105
return (Connection) Proxy.newProxyInstance(cl, new Class[] { Connection.class }, handler);
82106
}

src/main/java/org/apache/ibatis/logging/jdbc/PreparedStatementLogger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,7 +53,7 @@ public Object invoke(Object proxy, Method method, Object[] params) throws Throwa
5353
clearColumnInfo();
5454
if ("executeQuery".equals(method.getName())) {
5555
ResultSet rs = (ResultSet) method.invoke(statement, params);
56-
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
56+
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack, MASK_LOG_RESULT_COLUMNS);
5757
} else {
5858
return method.invoke(statement, params);
5959
}

src/main/java/org/apache/ibatis/logging/jdbc/ResultSetLogger.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,7 +55,11 @@ public final class ResultSetLogger extends BaseJdbcLogger implements InvocationH
5555
}
5656

5757
private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
58-
super(statementLog, queryStack);
58+
this(rs, statementLog, queryStack, null);
59+
}
60+
61+
private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack, Set<String> maskLogResultColumns) {
62+
super(statementLog, queryStack, maskLogResultColumns);
5963
this.rs = rs;
6064
}
6165

@@ -107,7 +111,10 @@ private void printColumnValues(int columnCount) {
107111
if (blobColumns.contains(i)) {
108112
row.add("<<BLOB>>");
109113
} else {
110-
row.add(rs.getString(i));
114+
String strVal = rs.getString(i);
115+
String column = rs.getMetaData().getColumnName(i);
116+
boolean mask = column != null && MASK_LOG_RESULT_COLUMNS.stream().anyMatch(column::equalsIgnoreCase);
117+
row.add(mask ? mask(strVal) : strVal);
111118
}
112119
} catch (SQLException e) {
113120
// generally can't call getString() on a BLOB column
@@ -130,7 +137,26 @@ private void printColumnValues(int columnCount) {
130137
* @return the ResultSet with logging
131138
*/
132139
public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
133-
InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
140+
return newInstance(rs, statementLog, queryStack, null);
141+
}
142+
143+
/**
144+
* Creates a logging version of a ResultSet.
145+
*
146+
* @param rs
147+
* the ResultSet to proxy
148+
* @param statementLog
149+
* the statement log
150+
* @param queryStack
151+
* the query stack
152+
* @param maskLogResultColumns
153+
* the result columns to be masked
154+
*
155+
* @return the ResultSet with logging
156+
*/
157+
public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack,
158+
Set<String> maskLogResultColumns) {
159+
InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack, maskLogResultColumns);
134160
ClassLoader cl = ResultSet.class.getClassLoader();
135161
return (ResultSet) Proxy.newProxyInstance(cl, new Class[] { ResultSet.class }, handler);
136162
}

src/main/java/org/apache/ibatis/logging/jdbc/StatementLogger.java

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Proxy;
2121
import java.sql.ResultSet;
2222
import java.sql.Statement;
23+
import java.util.Set;
2324

2425
import org.apache.ibatis.logging.Log;
2526
import org.apache.ibatis.reflection.ExceptionUtil;
@@ -35,7 +36,11 @@ public final class StatementLogger extends BaseJdbcLogger implements InvocationH
3536
private final Statement statement;
3637

3738
private StatementLogger(Statement stmt, Log statementLog, int queryStack) {
38-
super(statementLog, queryStack);
39+
this(stmt, statementLog, queryStack, null);
40+
}
41+
42+
private StatementLogger(Statement stmt, Log statementLog, int queryStack, Set<String> maskLogResultColumns) {
43+
super(statementLog, queryStack, maskLogResultColumns);
3944
this.statement = stmt;
4045
}
4146

@@ -51,14 +56,14 @@ public Object invoke(Object proxy, Method method, Object[] params) throws Throwa
5156
}
5257
if ("executeQuery".equals(method.getName())) {
5358
ResultSet rs = (ResultSet) method.invoke(statement, params);
54-
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
59+
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack, MASK_LOG_RESULT_COLUMNS);
5560
} else {
5661
return method.invoke(statement, params);
5762
}
5863
}
5964
if ("getResultSet".equals(method.getName())) {
6065
ResultSet rs = (ResultSet) method.invoke(statement, params);
61-
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
66+
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack, MASK_LOG_RESULT_COLUMNS);
6267
} else {
6368
return method.invoke(statement, params);
6469
}
@@ -80,7 +85,26 @@ public Object invoke(Object proxy, Method method, Object[] params) throws Throwa
8085
* @return the proxy
8186
*/
8287
public static Statement newInstance(Statement stmt, Log statementLog, int queryStack) {
83-
InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack);
88+
return newInstance(stmt, statementLog, queryStack, null);
89+
}
90+
91+
/**
92+
* Creates a logging version of a Statement.
93+
*
94+
* @param stmt
95+
* the statement
96+
* @param statementLog
97+
* the statement log
98+
* @param queryStack
99+
* the query stack
100+
* @param maskLogResultColumns
101+
* the result columns to be masked
102+
*
103+
* @return the proxy
104+
*/
105+
public static Statement newInstance(Statement stmt, Log statementLog, int queryStack,
106+
Set<String> maskLogResultColumns) {
107+
InvocationHandler handler = new StatementLogger(stmt, statementLog, queryStack, maskLogResultColumns);
84108
ClassLoader cl = Statement.class.getClassLoader();
85109
return (Statement) Proxy.newProxyInstance(cl, new Class[] { Statement.class }, handler);
86110
}

0 commit comments

Comments
 (0)