22
22
23
23
import com .google .common .flogger .LogContext ;
24
24
import com .google .common .flogger .LogSite ;
25
+ import com .google .common .flogger .MetadataKey ;
26
+ import com .google .common .flogger .backend .BaseMessageFormatter ;
25
27
import com .google .common .flogger .backend .LogData ;
26
28
import com .google .common .flogger .backend .MessageUtils ;
27
29
import com .google .common .flogger .backend .Metadata ;
30
+ import com .google .common .flogger .backend .MetadataHandler ;
28
31
import com .google .common .flogger .backend .MetadataProcessor ;
29
32
import com .google .common .flogger .backend .Platform ;
30
33
import com .google .common .flogger .backend .SimpleMessageFormatter ;
31
- import java . util . Collections ;
32
- import java . util . Map ;
34
+ import com . google . common . flogger . context . ScopedLoggingContext ;
35
+ import com . google . common . flogger . context . Tags ;
33
36
import java .util .logging .Level ;
37
+ import java .util .stream .Collectors ;
38
+ import java .util .stream .StreamSupport ;
34
39
import org .apache .logging .log4j .core .LogEvent ;
40
+ import org .apache .logging .log4j .core .LoggerContext ;
41
+ import org .apache .logging .log4j .core .config .Configuration ;
42
+ import org .apache .logging .log4j .core .config .DefaultConfiguration ;
43
+ import org .apache .logging .log4j .core .impl .ContextDataFactory ;
35
44
import org .apache .logging .log4j .core .impl .Log4jLogEvent ;
36
45
import org .apache .logging .log4j .core .time .Instant ;
37
46
import org .apache .logging .log4j .core .time .MutableInstant ;
38
47
import org .apache .logging .log4j .core .util .Throwables ;
39
48
import org .apache .logging .log4j .message .SimpleMessage ;
49
+ import org .apache .logging .log4j .util .StringMap ;
40
50
41
- /** Helper to format LogData.
51
+ /**
52
+ * Helper to format LogData.
42
53
*
43
54
* <p>Note: Any changes in this code should, as far as possible, be reflected in the equivalently
44
55
* named log4j implementation. If the behaviour of this class starts to deviate from that of the
@@ -52,10 +63,34 @@ private Log4j2LogEventUtil() {}
52
63
static LogEvent toLog4jLogEvent (String loggerName , LogData logData ) {
53
64
MetadataProcessor metadata =
54
65
MetadataProcessor .forScopeAndLogSite (Platform .getInjectedMetadata (), logData .getMetadata ());
55
- String message = SimpleMessageFormatter .getDefaultFormatter ().format (logData , metadata );
66
+
67
+ /*
68
+ * If no configuration file could be located, Log4j2 will use the DefaultConfiguration. This
69
+ * will cause logging output to go to the console and the context data will be ignored. This
70
+ * mechanism can be used to detect if a configuration file has been loaded (or if the default
71
+ * configuration was overwritten through the means of a configuration factory) by checking the
72
+ * type of the current configuration class.
73
+ *
74
+ * Be aware that the LoggerContext class is not part of Log4j2's public API and behavior can
75
+ * change with any minor release.
76
+ *
77
+ * For the future we are thinking about implementing a Flogger aware Log4j2 configuration (e.g.
78
+ * using a configuration builder with a custom ConfigurationFactory) to configure a formatter,
79
+ * which can perhaps be installed as default if nothing else is present. Then, we would not rely
80
+ * on Log4j2 internals.
81
+ */
82
+ LoggerContext ctx = LoggerContext .getContext (false );
83
+ Configuration config = ctx .getConfiguration ();
84
+ String message ;
85
+ if (config instanceof DefaultConfiguration ) {
86
+ message = SimpleMessageFormatter .getDefaultFormatter ().format (logData , metadata );
87
+ } else {
88
+ message =
89
+ BaseMessageFormatter .appendFormattedMessage (logData , new StringBuilder ()).toString ();
90
+ }
91
+
56
92
Throwable thrown = metadata .getSingleValue (LogContext .Key .LOG_CAUSE );
57
- return toLog4jLogEvent (
58
- loggerName , logData , message , toLog4jLevel (logData .getLevel ()), thrown );
93
+ return toLog4jLogEvent (loggerName , logData , message , toLog4jLevel (logData .getLevel ()), thrown );
59
94
}
60
95
61
96
static LogEvent toLog4jLogEvent (String loggerName , RuntimeException error , LogData badData ) {
@@ -71,21 +106,6 @@ private static LogEvent toLog4jLogEvent(
71
106
String message ,
72
107
org .apache .logging .log4j .Level level ,
73
108
Throwable thrown ) {
74
- // The Mapped Diagnostic Context (MDC) allows to include additional metadata into logs which
75
- // are written from the current thread.
76
- //
77
- // Example:
78
- // MDC.put("user.id", userId);
79
- // // do business logic that triggers logs
80
- // MDC.clear();
81
- //
82
- // By using '%X{key}' in the ConversionPattern of an appender this data can be included in the
83
- // logs.
84
- //
85
- // We could include this data here by doing 'MDC.getContext()', but we don't want to encourage
86
- // people using the log4j specific MDC. Instead this should be supported by a LoggingContext and
87
- // usage of Flogger tags.
88
- Map <String , String > mdcProperties = Collections .emptyMap ();
89
109
90
110
LogSite logSite = logData .getLogSite ();
91
111
StackTraceElement locationInfo =
@@ -105,7 +125,7 @@ private static LogEvent toLog4jLogEvent(
105
125
.setThrown (thrown != null ? Throwables .getRootCause (thrown ) : null )
106
126
.setIncludeLocation (true )
107
127
.setSource (locationInfo )
108
- .setContextMap ( mdcProperties )
128
+ .setContextData ( createContextMap ( logData ) )
109
129
.build ();
110
130
}
111
131
@@ -180,4 +200,63 @@ private static void appendLogData(LogData data, StringBuilder out) {
180
200
out .append ("\n method: " ).append (data .getLogSite ().getMethodName ());
181
201
out .append ("\n line number: " ).append (data .getLogSite ().getLineNumber ());
182
202
}
203
+
204
+ private static final MetadataHandler <MetadataKey .KeyValueHandler > HANDLER =
205
+ MetadataHandler .builder (
206
+ (MetadataHandler .ValueHandler <Object , MetadataKey .KeyValueHandler >)
207
+ (key , value , kvh ) -> {
208
+ if (key .getClass ().equals (LogContext .Key .TAGS .getClass ())) {
209
+ processTags (key , value , kvh );
210
+ } else {
211
+ // In theory a user can define a custom tag and use it as a MetadataKey. Those
212
+ // keys shall be treated in the same way as LogContext.Key.TAGS when used as a
213
+ // MetadataKey. Might be removed if visibility of MetadataKey#clazz changes.
214
+ if (value instanceof Tags ) {
215
+ processTags (key , value , kvh );
216
+ } else {
217
+ ValueQueue .appendValues (key .getLabel (), value , kvh );
218
+ }
219
+ }
220
+ })
221
+ .setDefaultRepeatedHandler (
222
+ (key , values , kvh ) -> values .forEachRemaining (v -> kvh .handle (key .getLabel (), v )))
223
+ .build ();
224
+
225
+ private static void processTags (
226
+ MetadataKey <Object > key , Object value , MetadataKey .KeyValueHandler kvh ) {
227
+ ValueQueue valueQueue = ValueQueue .appendValueToNewQueue (value );
228
+ // Unlike single metadata (which is usually formatted as a single value), tags are always
229
+ // formatted as a list.
230
+ // Given the tags: tags -> foo=[bar], it will be formatted as tags=[foo=bar].
231
+ ValueQueue .appendValues (
232
+ key .getLabel (),
233
+ valueQueue .size () == 1
234
+ ? StreamSupport .stream (valueQueue .spliterator (), false ).collect (Collectors .toList ())
235
+ : valueQueue ,
236
+ kvh );
237
+ }
238
+
239
+ /**
240
+ * We do not support {@code MDC.getContext()} and {@code NDC.getStack()} and we do not make any
241
+ * attempt to merge Log4j2 context data with Flogger's context data. Instead, users should use the
242
+ * {@link ScopedLoggingContext}.
243
+ *
244
+ * <p>Flogger's {@link ScopedLoggingContext} allows to include additional metadata and tags into
245
+ * logs which are written from current thread. This context data will be added to the log4j2
246
+ * event.
247
+ */
248
+ private static StringMap createContextMap (LogData logData ) {
249
+ MetadataProcessor metadataProcessor =
250
+ MetadataProcessor .forScopeAndLogSite (Platform .getInjectedMetadata (), logData .getMetadata ());
251
+
252
+ StringMap contextData = ContextDataFactory .createContextData (metadataProcessor .keyCount ());
253
+ metadataProcessor .process (
254
+ HANDLER ,
255
+ (key , value ) ->
256
+ contextData .putValue (key , ValueQueue .maybeWrap (value , contextData .getValue (key ))));
257
+
258
+ contextData .freeze ();
259
+
260
+ return contextData ;
261
+ }
183
262
}
0 commit comments