1
+ /* Copyright (c) 2025 LibJ
2
+ *
3
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ * of this software and associated documentation files (the "Software"), to deal
5
+ * in the Software without restriction, including without limitation the rights
6
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ * copies of the Software, and to permit persons to whom the Software is
8
+ * furnished to do so, subject to the following conditions:
9
+ *
10
+ * The above copyright notice and this permission notice shall be included in
11
+ * all copies or substantial portions of the Software.
12
+ *
13
+ * You should have received a copy of The MIT License (MIT) along with this
14
+ * program. If not, see <http://opensource.org/licenses/MIT/>.
15
+ */
16
+
17
+ package org .libj .logging ;
18
+
19
+ import java .util .ArrayList ;
20
+ import java .util .Arrays ;
21
+ import java .util .List ;
22
+ import java .util .Map ;
23
+
24
+ import ch .qos .logback .classic .spi .ILoggingEvent ;
25
+ import ch .qos .logback .core .Context ;
26
+ import ch .qos .logback .core .pattern .CompositeConverter ;
27
+
28
+ /**
29
+ * A {@link CompositeConverter} that allows for the conversion of a log pattern to be displayed in colors configurable via
30
+ * {@code <variable>} tags.
31
+ * <p>
32
+ * Example:
33
+ *
34
+ * <pre>
35
+ * <variable scope="context" name="cc:com.example.foo" value="[38;5;21m"/>
36
+ * <variable scope="context" name="cc:com.example.bar" value="[38;5;219m"/>
37
+ * <conversionRule conversionWord="customColor" converterClass="org.libj.logging.CustomColorConverter"/>
38
+ * <appender>
39
+ * <encoder>
40
+ * <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %customColor(%logger){loggerName,cc} %color(%level %msg%n)</pattern>
41
+ * </encoder>
42
+ * </appender>
43
+ * </pre>
44
+ * <p>
45
+ * In the provided example, two options are specified:
46
+ * <ol>
47
+ * <li>loggerName: The event property to match. Can be one of {level, loggerName, marker, message, threadName}.</li>
48
+ * <li>cc: The variable prefix to be referenced when parsing defined variables.</li>
49
+ * </ol>
50
+ * <p>
51
+ * The following two variables are also defined:
52
+ * <ol>
53
+ * <li>cc:com.example.foo: The key with "cc:" prefix to be matched resulting in the color specified as the value when matching
54
+ * "com.example.foo" as the "loggerName".</li>
55
+ * <li>cc:com.example.bar: The key with "cc:" prefix to be matched resulting in the color specified as the value when matching
56
+ * "com.example.bar" as the "loggerName"</li>
57
+ * </ol>
58
+ */
59
+ public class CustomColorConverter extends CompositeConverter <ILoggingEvent > {
60
+ private static final String esc = String .valueOf ((char )0x1b );
61
+
62
+ private final ArrayList <String > keys = new ArrayList <>();
63
+ private final ArrayList <String > values = new ArrayList <>();
64
+
65
+ private enum Property {
66
+ LEVEL ("level" ) {
67
+ @ Override
68
+ String getValue (final ILoggingEvent event ) {
69
+ return event .getLevel ().toString ();
70
+ }
71
+ },
72
+ LOGGER_NAME ("loggerName" ) {
73
+ @ Override
74
+ String getValue (final ILoggingEvent event ) {
75
+ return event .getLoggerName ();
76
+ }
77
+ },
78
+ MARKER ("marker" ) {
79
+ @ Override
80
+ String getValue (final ILoggingEvent event ) {
81
+ return event .getMarker ().toString ();
82
+ }
83
+ },
84
+ MESSAGE ("message" ) {
85
+ @ Override
86
+ String getValue (final ILoggingEvent event ) {
87
+ return event .getMessage ();
88
+ }
89
+ },
90
+ THREAD_NAME ("threadName" ) {
91
+ @ Override
92
+ String getValue (final ILoggingEvent event ) {
93
+ return event .getThreadName ();
94
+ }
95
+ };
96
+
97
+ private final String name ;
98
+
99
+ private Property (final String name ) {
100
+ this .name = name ;
101
+ }
102
+
103
+ abstract String getValue (ILoggingEvent event );
104
+
105
+ @ Override
106
+ public String toString () {
107
+ return name ;
108
+ }
109
+
110
+ private static Property fromString (final String s ) {
111
+ for (final Property property : values ()) // [A]
112
+ if (property .name .equals (s ))
113
+ return property ;
114
+
115
+ return null ;
116
+ }
117
+ }
118
+
119
+ private Property property ;
120
+
121
+ @ Override
122
+ public void start () {
123
+ final Context context = getContext ();
124
+ if (context == null )
125
+ return ;
126
+
127
+ final List <String > optionList = super .getOptionList ();
128
+ if (optionList == null || optionList .size () != 2 ) {
129
+ addError ("Options {<EVENT_PROPERTY>,<VARIABLE_PREFIX>} are not specified for conversionRule with class " + getClass ().getName ());
130
+ return ;
131
+ }
132
+
133
+ final String propertyName = optionList .get (0 );
134
+ this .property = Property .fromString (propertyName );
135
+ if (this .property == null ) {
136
+ addError ("EVENT_PROPERTY (" + propertyName + ") does not match: " + Arrays .toString (Property .values ()));
137
+ return ;
138
+ }
139
+
140
+ final String prefix = optionList .get (1 ) + ":" ;
141
+ final int len = prefix .length ();
142
+ for (final Map .Entry <String ,String > entry : context .getCopyOfPropertyMap ().entrySet ()) {
143
+ if (entry .getKey ().startsWith (prefix )) {
144
+ final String key = entry .getKey ().substring (len );
145
+ keys .add (key );
146
+ values .add (entry .getValue ());
147
+ }
148
+ }
149
+
150
+ super .start ();
151
+ }
152
+
153
+ private String match (final ILoggingEvent event ) {
154
+ final String loggerName = event .getLoggerName ();
155
+ final int length = loggerName .length ();
156
+ for (int i = 0 , i$ = keys .size (); i < i$ ; ++i ) { // [RA]
157
+ final String key = keys .get (i );
158
+ final int len = key .length ();
159
+ final String value = values .get (i );
160
+ if (len == length ) {
161
+ if (loggerName .equals (value ))
162
+ return value ;
163
+ }
164
+ else if (len < length ) {
165
+ if (loggerName .startsWith (key ) && loggerName .charAt (len ) == '.' )
166
+ return value ;
167
+ }
168
+ }
169
+
170
+ return null ;
171
+ }
172
+
173
+ @ Override
174
+ protected String transform (final ILoggingEvent event , final String in ) {
175
+ final String value = match (event );
176
+ return value != null ? esc + value + in + esc + "[0m" : in ;
177
+ }
178
+ }
0 commit comments