Skip to content

Commit f33c529

Browse files
authored
Merge pull request #553 from CC11001100/fix/log-format
fix: dongtai log format
2 parents 0e10d98 + 5801bc7 commit f33c529

File tree

5 files changed

+188
-12
lines changed

5 files changed

+188
-12
lines changed

dongtai-log/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@
4545
<version>${junit.version}</version>
4646
<scope>test</scope>
4747
</dependency>
48+
<dependency>
49+
<groupId>org.openjdk.jmh</groupId>
50+
<artifactId>jmh-core</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.openjdk.jmh</groupId>
55+
<artifactId>jmh-generator-annprocess</artifactId>
56+
<scope>test</scope>
57+
</dependency>
4858
</dependencies>
4959

5060
</project>

dongtai-log/src/main/java/io/dongtai/log/DongTaiLog.java

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import java.util.*;
99
import java.util.concurrent.ConcurrentHashMap;
1010
import java.util.logging.Level;
11-
import java.util.regex.Matcher;
1211

1312
/**
1413
@@ -296,17 +295,98 @@ public static void error(String format, Object... arguments) {
296295
log(LogLevel.ERROR, ErrorCode.NO_CODE, format, arguments);
297296
}
298297

299-
private static String format(String from, Object... arguments) {
300-
if (from != null) {
301-
String computed = from;
302-
if (arguments != null && arguments.length != 0) {
303-
for (Object argument : arguments) {
304-
computed = computed.replaceFirst("\\{\\}", argument == null ? "NULL" : Matcher.quoteReplacement(argument.toString()));
305-
}
298+
/**
299+
* 格式化字符串
300+
*
301+
* @param pattern 用来格式化的pattern,比如 "my name is {}, today is {}"
302+
* @param arguments 用来填充上面的pattern的参数,比如 []String{"CC11001100", "2023-7-12"}
303+
* @return 格式化之后的字符串,比如 "my name is CC11001100, today is 2023-7-12"
304+
*/
305+
static String format(String pattern, Object... arguments) {
306+
// if (from != null) {
307+
// String computed = from;
308+
// if (arguments != null && arguments.length != 0) {
309+
// for (Object argument : arguments) {
310+
// computed = computed.replaceFirst("\\{\\}", argument == null ? "NULL" : Matcher.quoteReplacement(argument.toString()));
311+
// }
312+
// }
313+
// return computed;
314+
// }
315+
// return null;
316+
317+
// 如果没有参数格式化的话快速退出
318+
if (arguments.length == 0 || pattern == null) {
319+
return pattern;
320+
}
321+
322+
// 先把参数都转为字符串,并且预估参数的字符长度
323+
int argumentCharCount = 0;
324+
String[] argumentStrings = new String[arguments.length];
325+
for (int i = 0; i < arguments.length; i++) {
326+
argumentStrings[i] = arguments[i] == null ? "NULL" : arguments[i].toString();
327+
argumentCharCount += argumentStrings[i].length();
328+
}
329+
330+
// 现在,就能算出结果的长度避免扩容了,这里没有使用减去 参数长度乘以2的占位符长度 是因为pattern中占位符的实际个数可能并不足以消费完参数
331+
// (因为并没有强制pattern里的占位符与参数一一对应,所以它们的个数是很随意的),这种情况下如果做乐观假设的话可能会导致特殊情况下扩容,
332+
// 所以此处采取比较保守的策略,宁愿在特殊情况下浪费几个字节的空间也不愿在特殊情况下扩容
333+
// 举一个具体的例子,比如传入的pattern: "a{}c", 传入的参数 String[]{"b", "d", .. , "z"},参数可能有几十个,远超占位符的个数,则此时会被减去错误的长度,甚至导致长度为负
334+
// StringBuilder buff = new StringBuilder(pattern.length() - argumentStrings.length * 2 + argumentCharCount);
335+
StringBuilder buff = new StringBuilder(pattern.length() + argumentCharCount);
336+
// 下一个被消费的参数的下标
337+
int argumentConsumeIndex = 0;
338+
for (int i = 0; i < pattern.length(); i++) {
339+
char c = pattern.charAt(i);
340+
switch (c) {
341+
case '\\':
342+
// 如果是转义字符的话,则看下它在转义个啥
343+
if (i + 1 < pattern.length()) {
344+
// 非最后一个字符,则根据不同的转义保留不同的字符,并且一次消费两个字符
345+
char nextChar = pattern.charAt(++i);
346+
switch (nextChar) {
347+
case '{':
348+
case '}':
349+
// 只保留被转义的左花括号或者右花括号,转义字符本身将被丢弃
350+
buff.append(nextChar);
351+
break;
352+
default:
353+
// 转移的是其它字符,则原样保持转义,相当于是在通用转义的基础上扩展了对左右花括号的转义
354+
buff.append(c).append(nextChar);
355+
break;
356+
}
357+
} else {
358+
// 最后一个字符了,原样保留
359+
buff.append(c);
360+
}
361+
break;
362+
case '{':
363+
// 如果是左括号的话,则看一下是否是个占位符
364+
if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '}') {
365+
// 一次消费两个字符,把占位符 {} 消费掉
366+
i++;
367+
// 使用参数替换
368+
buff.append(argumentStrings[argumentConsumeIndex++]);
369+
// 实际传递的参数个数和pattern中的占位符数量可能会不匹配,参数更多的时候没问题多余的将被忽略
370+
// 参数更少的时候继续也没啥意义了,所以如果参数被消费完了则快速退出
371+
if (argumentConsumeIndex == argumentStrings.length) {
372+
// 把 } 后边的一股脑儿消费了,如果有的话
373+
if (i + 1 < pattern.length()) {
374+
buff.append(pattern.substring(i + 1, pattern.length()));
375+
}
376+
return buff.toString();
377+
}
378+
} else {
379+
// 最后一个字符或者不是匹配的右花括号,原样保留
380+
buff.append(c);
381+
}
382+
break;
383+
default:
384+
// 普通字符,原样复制
385+
buff.append(c);
386+
break;
306387
}
307-
return computed;
308388
}
309-
return null;
389+
return buff.toString();
310390
}
311391

312392
private static String getTime() {
@@ -315,6 +395,7 @@ private static String getTime() {
315395
return simpleDateFormat.format(new Date()) + " ";
316396
}
317397

398+
// TODO 2023-7-12 12:00:06 写日志文件效率问题
318399
private static void writeLogToFile(String msg, Throwable t) {
319400
if (LOG_PATH == null || LOG_PATH.isEmpty()) {
320401
return;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.dongtai.log;
2+
3+
import org.openjdk.jmh.annotations.*;
4+
import org.openjdk.jmh.results.format.ResultFormatType;
5+
import org.openjdk.jmh.runner.Runner;
6+
import org.openjdk.jmh.runner.options.Options;
7+
import org.openjdk.jmh.runner.options.OptionsBuilder;
8+
9+
import java.util.concurrent.TimeUnit;
10+
11+
/**
12+
* @author CC11001100
13+
*/
14+
@BenchmarkMode(Mode.All)
15+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
16+
@State(Scope.Thread)
17+
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
18+
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
19+
@Threads(1)
20+
public class DongTaiLogBenchmarkTest {
21+
22+
@Benchmark
23+
public void formatTest() {
24+
DongTaiLog.format("{} {} {}", "foo", "123", "bar");
25+
}
26+
27+
public static void main(String[] args) throws Exception {
28+
Options opts = new OptionsBuilder()
29+
.include(DongTaiLogBenchmarkTest.class.getSimpleName())
30+
.resultFormat(ResultFormatType.JSON)
31+
.build();
32+
new Runner(opts).run();
33+
}
34+
35+
}

dongtai-log/src/test/java/io.dongtai.log/DongTaiLogTest.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package io.dongtai.log;
22

3-
import org.junit.*;
3+
import org.junit.After;
4+
import org.junit.Assert;
5+
import org.junit.Before;
6+
import org.junit.Test;
47

58
import java.io.ByteArrayOutputStream;
69
import java.io.PrintStream;
@@ -265,4 +268,36 @@ public void logTest() {
265268

266269
clear();
267270
}
271+
272+
@Test
273+
public void formatTest() {
274+
275+
Assert.assertEquals("this is foo, i am ok", DongTaiLog.format("this is {}, i am ok", "foo"));
276+
Assert.assertEquals("this is {} foo {}, i am ok", DongTaiLog.format("this is {}, i am ok", "{} foo {}"));
277+
278+
Assert.assertEquals("foo", DongTaiLog.format("{}", "foo"));
279+
Assert.assertEquals("foo begin", DongTaiLog.format("{} begin", "foo"));
280+
Assert.assertEquals("end foo", DongTaiLog.format("end {}", "foo"));
281+
282+
Assert.assertEquals("foobar", DongTaiLog.format("{}{}", "foo", "bar"));
283+
Assert.assertEquals("foo123bar", DongTaiLog.format("{}{}{}", "foo", "123", "bar"));
284+
Assert.assertEquals("foo 123 bar", DongTaiLog.format("{} {} {}", "foo", "123", "bar"));
285+
286+
Assert.assertEquals("{}", DongTaiLog.format("\\{}", "foo"));
287+
Assert.assertEquals("{}", DongTaiLog.format("{\\}", "foo"));
288+
289+
Assert.assertEquals("{", DongTaiLog.format("{", "foo"));
290+
Assert.assertEquals("foo {", DongTaiLog.format("foo {", "foo"));
291+
Assert.assertEquals("{ foo", DongTaiLog.format("{ foo", "foo"));
292+
Assert.assertEquals("}", DongTaiLog.format("}", "foo"));
293+
Assert.assertEquals("}{", DongTaiLog.format("}{", "foo"));
294+
Assert.assertEquals("}{", DongTaiLog.format("}{", "foo"));
295+
296+
// 参数不够
297+
Assert.assertEquals("foo {} {}", DongTaiLog.format("{} {} {}", "foo"));
298+
// 参数过多
299+
Assert.assertEquals("foo", DongTaiLog.format("{}", "foo", "bar"));
300+
301+
}
302+
268303
}

pom.xml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
44
<modelVersion>4.0.0</modelVersion>
55

66
<properties>
@@ -38,6 +38,7 @@
3838
<!-- gson latest version for JDK6 -->
3939
<gson.version>2.8.9</gson.version>
4040
<fastjson2.version>2.0.28</fastjson2.version>
41+
<jmh.version>1.23</jmh.version>
4142
</properties>
4243

4344
<groupId>io.dongtai.iast</groupId>
@@ -157,6 +158,20 @@
157158
<version>${project.version}</version>
158159
<scope>test</scope>
159160
</dependency>
161+
162+
<!-- 微基准测试 -->
163+
<dependency>
164+
<groupId>org.openjdk.jmh</groupId>
165+
<artifactId>jmh-core</artifactId>
166+
<version>${jmh.version}</version>
167+
<scope>test</scope>
168+
</dependency>
169+
<dependency>
170+
<groupId>org.openjdk.jmh</groupId>
171+
<artifactId>jmh-generator-annprocess</artifactId>
172+
<version>${jmh.version}</version>
173+
<scope>test</scope>
174+
</dependency>
160175
</dependencies>
161176
</dependencyManagement>
162177

0 commit comments

Comments
 (0)