17
17
package org .springframework .boot .logging .logback ;
18
18
19
19
import java .math .BigDecimal ;
20
- import java .util .Map ;
21
20
import java .util .Objects ;
22
21
import java .util .Set ;
23
- import java .util .function .Function ;
22
+ import java .util .function .BiConsumer ;
24
23
import java .util .regex .Pattern ;
25
- import java .util .stream .Collectors ;
26
24
27
25
import ch .qos .logback .classic .pattern .ThrowableProxyConverter ;
28
26
import ch .qos .logback .classic .spi .ILoggingEvent ;
29
27
import ch .qos .logback .classic .spi .IThrowableProxy ;
30
28
import ch .qos .logback .classic .util .LevelToSyslogSeverity ;
31
- import org .slf4j .event .KeyValuePair ;
29
+ import org .apache .commons .logging .Log ;
30
+ import org .apache .commons .logging .LogFactory ;
32
31
33
32
import org .springframework .boot .json .JsonWriter ;
34
- import org .springframework .boot .json .JsonWriter .PairExtractor ;
33
+ import org .springframework .boot .json .JsonWriter .WritableJson ;
35
34
import org .springframework .boot .logging .structured .CommonStructuredLogFormat ;
36
35
import org .springframework .boot .logging .structured .GraylogExtendedLogFormatService ;
37
36
import org .springframework .boot .logging .structured .JsonWriterStructuredLogFormatter ;
38
37
import org .springframework .boot .logging .structured .StructuredLogFormatter ;
39
38
import org .springframework .core .env .Environment ;
39
+ import org .springframework .core .log .LogMessage ;
40
40
import org .springframework .util .Assert ;
41
+ import org .springframework .util .CollectionUtils ;
41
42
42
43
/**
43
44
* Logback {@link StructuredLogFormatter} for
44
45
* {@link CommonStructuredLogFormat#GRAYLOG_EXTENDED_LOG_FORMAT}. Supports GELF version
45
46
* 1.1.
46
47
*
47
48
* @author Samuel Lissner
49
+ * @author Moritz Halbritter
48
50
*/
49
51
class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructuredLogFormatter <ILoggingEvent > {
50
52
53
+ private static final Log logger = LogFactory .getLog (GraylogExtendedLogFormatStructuredLogFormatter .class );
54
+
51
55
/**
52
56
* Allowed characters in field names are any word character (letter, number,
53
57
* underscore), dashes and dots.
54
58
*/
55
- private static final Pattern FIELD_NAME_VALID_PATTERN = Pattern .compile ("^[\\ w\\ .\\ -]*$" );
59
+ private static final Pattern FIELD_NAME_VALID_PATTERN = Pattern .compile ("^[\\ w.\\ -]*$" );
56
60
57
61
/**
58
62
* Every field been sent and prefixed with an underscore "_" will be treated as an
@@ -64,16 +68,7 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
64
68
* Libraries SHOULD not allow to send id as additional field ("_id"). Graylog server
65
69
* nodes omit this field automatically.
66
70
*/
67
- private static final Set <String > ADDITIONAL_FIELD_ILLEGAL_KEYS = Set .of ("_id" );
68
-
69
- /**
70
- * Default format to be used for the `full_message` property when there is a throwable
71
- * present in the log event.
72
- */
73
- private static final String DEFAULT_FULL_MESSAGE_WITH_THROWABLE_FORMAT = "%s%n%n%s" ;
74
-
75
- private static final PairExtractor <KeyValuePair > keyValuePairExtractor = PairExtractor
76
- .of ((pair ) -> makeAdditionalFieldName (pair .key ), (pair ) -> pair .value );
71
+ private static final Set <String > ADDITIONAL_FIELD_ILLEGAL_KEYS = Set .of ("id" , "_id" );
77
72
78
73
GraylogExtendedLogFormatStructuredLogFormatter (Environment environment ,
79
74
ThrowableProxyConverter throwableProxyConverter ) {
@@ -83,30 +78,25 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
83
78
private static void jsonMembers (Environment environment , ThrowableProxyConverter throwableProxyConverter ,
84
79
JsonWriter .Members <ILoggingEvent > members ) {
85
80
members .add ("version" , "1.1" );
86
-
87
81
// note: a blank message will lead to a Graylog error as of Graylog v6.0.x. We are
88
82
// ignoring this here.
89
83
members .add ("short_message" , ILoggingEvent ::getFormattedMessage );
90
-
91
84
members .add ("timestamp" , ILoggingEvent ::getTimeStamp )
92
85
.as (GraylogExtendedLogFormatStructuredLogFormatter ::formatTimeStamp );
93
86
members .add ("level" , LevelToSyslogSeverity ::convert );
94
87
members .add ("_level_name" , ILoggingEvent ::getLevel );
95
-
96
88
members .add ("_process_pid" , environment .getProperty ("spring.application.pid" , Long .class ))
97
89
.when (Objects ::nonNull );
98
90
members .add ("_process_thread_name" , ILoggingEvent ::getThreadName );
99
-
100
91
GraylogExtendedLogFormatService .get (environment ).jsonMembers (members );
101
-
102
92
members .add ("_log_logger" , ILoggingEvent ::getLoggerName );
103
-
104
- members . addMapEntries ( mapMDCProperties ( ILoggingEvent :: getMDCPropertyMap ));
105
-
93
+ members . from ( ILoggingEvent :: getMDCPropertyMap )
94
+ . when (( mdc ) -> ! CollectionUtils . isEmpty ( mdc ))
95
+ . usingPairs (( mdc , pairs ) -> mdc . forEach (( key , value ) -> createAdditionalField ( key , value , pairs )));
106
96
members .from (ILoggingEvent ::getKeyValuePairs )
107
- .whenNotEmpty ( )
108
- .usingExtractedPairs ( Iterable :: forEach , keyValuePairExtractor );
109
-
97
+ .when (( keyValuePairs ) -> ! CollectionUtils . isEmpty ( keyValuePairs ) )
98
+ .usingPairs (( keyValuePairs , pairs ) -> keyValuePairs
99
+ . forEach (( keyValuePair ) -> createAdditionalField ( keyValuePair . key , keyValuePair . value , pairs )));
110
100
members .add ().whenNotNull (ILoggingEvent ::getThrowableProxy ).usingMembers ((throwableMembers ) -> {
111
101
throwableMembers .add ("full_message" ,
112
102
(event ) -> formatFullMessageWithThrowable (throwableProxyConverter , event ));
@@ -123,38 +113,27 @@ private static void jsonMembers(Environment environment, ThrowableProxyConverter
123
113
* @param timeStamp the timestamp of the log message
124
114
* @return the timestamp formatted as string with millisecond precision
125
115
*/
126
- private static double formatTimeStamp (final long timeStamp ) {
127
- return new BigDecimal (timeStamp ).movePointLeft (3 ).doubleValue ( );
116
+ private static WritableJson formatTimeStamp (long timeStamp ) {
117
+ return ( out ) -> out . append ( new BigDecimal (timeStamp ).movePointLeft (3 ).toPlainString () );
128
118
}
129
119
130
- private static String formatFullMessageWithThrowable (final ThrowableProxyConverter throwableProxyConverter ,
120
+ private static String formatFullMessageWithThrowable (ThrowableProxyConverter throwableProxyConverter ,
131
121
ILoggingEvent event ) {
132
- return String .format (DEFAULT_FULL_MESSAGE_WITH_THROWABLE_FORMAT , event .getFormattedMessage (),
133
- throwableProxyConverter .convert (event ));
122
+ return event .getFormattedMessage () + "\n \n " + throwableProxyConverter .convert (event );
134
123
}
135
124
136
- private static Function <ILoggingEvent , Map <String , String >> mapMDCProperties (
137
- Function <ILoggingEvent , Map <String , String >> MDCPropertyMapGetter ) {
138
- return MDCPropertyMapGetter .andThen ((mdc ) -> mdc .entrySet ()
139
- .stream ()
140
- .collect (Collectors .toMap ((entry ) -> makeAdditionalFieldName (entry .getKey ()), Map .Entry ::getValue )));
141
- }
142
-
143
- private static String makeAdditionalFieldName (String fieldName ) {
144
- Assert .notNull (fieldName , "fieldName must not be null" );
145
- Assert .isTrue (FIELD_NAME_VALID_PATTERN .matcher (fieldName ).matches (),
146
- () -> String .format ("fieldName must be a valid according to GELF standard. [fieldName=%s]" , fieldName ));
147
- Assert .isTrue (!ADDITIONAL_FIELD_ILLEGAL_KEYS .contains (fieldName ), () -> String .format (
148
- "fieldName must not be an illegal additional field key according to GELF standard. [fieldName=%s]" ,
149
- fieldName ));
150
-
151
- if (fieldName .startsWith (ADDITIONAL_FIELD_PREFIX )) {
152
- // No need to prepend the `ADDITIONAL_FIELD_PREFIX` in case the caller already
153
- // has prepended the prefix.
154
- return fieldName ;
125
+ private static void createAdditionalField (String key , Object value , BiConsumer <Object , Object > pairs ) {
126
+ Assert .notNull (key , "fieldName must not be null" );
127
+ if (!FIELD_NAME_VALID_PATTERN .matcher (key ).matches ()) {
128
+ logger .warn (LogMessage .format ("'%s' is not a valid field name according to GELF standard" , key ));
129
+ return ;
155
130
}
156
-
157
- return ADDITIONAL_FIELD_PREFIX + fieldName ;
131
+ if (ADDITIONAL_FIELD_ILLEGAL_KEYS .contains (key )) {
132
+ logger .warn (LogMessage .format ("'%s' is an illegal field name according to GELF standard" , key ));
133
+ return ;
134
+ }
135
+ String keyWithPrefix = (key .startsWith (ADDITIONAL_FIELD_PREFIX )) ? key : ADDITIONAL_FIELD_PREFIX + key ;
136
+ pairs .accept (keyWithPrefix , value );
158
137
}
159
138
160
139
}
0 commit comments