@ -23,7 +23,10 @@ import ch.qos.logback.classic.spi.LoggingEvent;
@@ -23,7 +23,10 @@ import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy ;
import org.junit.jupiter.api.BeforeEach ;
import org.junit.jupiter.api.Test ;
import org.junit.jupiter.api.extension.ExtendWith ;
import org.springframework.boot.testsupport.system.CapturedOutput ;
import org.springframework.boot.testsupport.system.OutputCaptureExtension ;
import org.springframework.mock.env.MockEnvironment ;
import static org.assertj.core.api.Assertions.assertThat ;
@ -32,7 +35,9 @@ import static org.assertj.core.api.Assertions.assertThat;
@@ -32,7 +35,9 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for { @link GraylogExtendedLogFormatStructuredLogFormatter } .
*
* @author Samuel Lissner
* @author Moritz Halbritter
* /
@ExtendWith ( OutputCaptureExtension . class )
class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStructuredLoggingTests {
private GraylogExtendedLogFormatStructuredLogFormatter formatter ;
@ -44,8 +49,6 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
@@ -44,8 +49,6 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
MockEnvironment environment = new MockEnvironment ( ) ;
environment . setProperty ( "logging.structured.gelf.service.name" , "name" ) ;
environment . setProperty ( "logging.structured.gelf.service.version" , "1.0.0" ) ;
environment . setProperty ( "logging.structured.gelf.service.environment" , "test" ) ;
environment . setProperty ( "logging.structured.gelf.service.node-name" , "node-1" ) ;
environment . setProperty ( "spring.application.pid" , "1" ) ;
this . formatter = new GraylogExtendedLogFormatStructuredLogFormatter ( environment , getThrowableProxyConverter ( ) ) ;
}
@ -58,10 +61,63 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
@@ -58,10 +61,63 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
String json = this . formatter . format ( event ) ;
assertThat ( json ) . endsWith ( "\n" ) ;
Map < String , Object > deserialized = deserialize ( json ) ;
assertThat ( deserialized ) . containsExactlyInAnyOrderEntriesOf (
map ( "version" , "1.1" , "host" , "name" , "timestamp" , 1719910193 . 0 , "level" , 6 , "_level_name" , "INFO" ,
"_process_pid" , 1 , "_process_thread_name" , "main" , "_service_version" , "1.0.0" , "_log_logger" ,
"org.example.Test" , "short_message" , "message" , "_mdc-1" , "mdc-v-1" , "_kv-1" , "kv-v-1" ) ) ;
}
@Test
void shouldFormatMillisecondsInTimestamp ( ) {
LoggingEvent event = createEvent ( ) ;
event . setTimeStamp ( 1719910193123L ) ;
event . setMDCPropertyMap ( Collections . emptyMap ( ) ) ;
String json = this . formatter . format ( event ) ;
assertThat ( json ) . contains ( "\"timestamp\":1719910193.123" ) ;
assertThat ( json ) . endsWith ( "\n" ) ;
Map < String , Object > deserialized = deserialize ( json ) ;
assertThat ( deserialized ) . containsExactlyInAnyOrderEntriesOf ( map ( "version" , "1.1" , "host" , "name" , "timestamp" ,
1719910193 . 123 , "level" , 6 , "_level_name" , "INFO" , "_process_pid" , 1 , "_process_thread_name" , "main" ,
"_service_version" , "1.0.0" , "_log_logger" , "org.example.Test" , "short_message" , "message" ) ) ;
}
@Test
void shouldNotAllowInvalidFieldNames ( CapturedOutput output ) {
LoggingEvent event = createEvent ( ) ;
event . setMDCPropertyMap ( Map . of ( "/" , "value" ) ) ;
String json = this . formatter . format ( event ) ;
assertThat ( json ) . endsWith ( "\n" ) ;
Map < String , Object > deserialized = deserialize ( json ) ;
assertThat ( deserialized ) . containsExactlyInAnyOrderEntriesOf ( map ( "version" , "1.1" , "host" , "name" , "timestamp" ,
1719910193 . 0 , "level" , 6 , "_level_name" , "INFO" , "_process_pid" , 1 , "_process_thread_name" , "main" ,
"_service_version" , "1.0.0" , "_log_logger" , "org.example.Test" , "short_message" , "message" ) ) ;
assertThat ( output ) . contains ( "'/' is not a valid field name according to GELF standard" ) ;
}
@Test
void shouldNotAllowIllegalFieldNames ( CapturedOutput output ) {
LoggingEvent event = createEvent ( ) ;
event . setMDCPropertyMap ( Map . of ( "id" , "1" ) ) ;
String json = this . formatter . format ( event ) ;
assertThat ( json ) . endsWith ( "\n" ) ;
Map < String , Object > deserialized = deserialize ( json ) ;
assertThat ( deserialized ) . containsExactlyInAnyOrderEntriesOf ( map ( "version" , "1.1" , "host" , "name" , "timestamp" ,
1719910193 . 000D , "level" , 6 , "_level_name" , "INFO" , "_process_pid" , 1 , "_process_thread_name" , "main" ,
"_service_version" , "1.0.0" , "_service_environment" , "test" , "_service_node_name" , "node-1" ,
"_log_logger" , "org.example.Test" , "short_message" , "message" , "_mdc-1" , "mdc-v-1" , "_kv-1" , "kv-v-1" ) ) ;
1719910193 . 0 , "level" , 6 , "_level_name" , "INFO" , "_process_pid" , 1 , "_process_thread_name" , "main" ,
"_service_version" , "1.0.0" , "_log_logger" , "org.example.Test" , "short_message" , "message" ) ) ;
assertThat ( output ) . contains ( "'id' is an illegal field name according to GELF standard" ) ;
}
@Test
void shouldNotAddDoubleUnderscoreToCustomFields ( ) {
LoggingEvent event = createEvent ( ) ;
event . setMDCPropertyMap ( Map . of ( "_custom" , "value" ) ) ;
String json = this . formatter . format ( event ) ;
assertThat ( json ) . endsWith ( "\n" ) ;
Map < String , Object > deserialized = deserialize ( json ) ;
assertThat ( deserialized ) . containsExactlyInAnyOrderEntriesOf (
map ( "version" , "1.1" , "host" , "name" , "timestamp" , 1719910193 . 0 , "level" , 6 , "_level_name" , "INFO" ,
"_process_pid" , 1 , "_process_thread_name" , "main" , "_service_version" , "1.0.0" , "_log_logger" ,
"org.example.Test" , "short_message" , "message" , "_custom" , "value" ) ) ;
}
@Test
@ -69,16 +125,13 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
@@ -69,16 +125,13 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
LoggingEvent event = createEvent ( ) ;
event . setMDCPropertyMap ( Collections . emptyMap ( ) ) ;
event . setThrowableProxy ( new ThrowableProxy ( new RuntimeException ( "Boom" ) ) ) ;
String json = this . formatter . format ( event ) ;
Map < String , Object > deserialized = deserialize ( json ) ;
String fullMessage = ( String ) deserialized . get ( "full_message" ) ;
String stackTrace = ( String ) deserialized . get ( "_error_stack_trace" ) ;
assertThat ( fullMessage ) . startsWith (
"message\n\njava.lang.RuntimeException: Boom%n\tat org.springframework.boot.logging.logback.GraylogExtendedLogFormatStructuredLogFormatterTests.shouldFormatException"
. formatted ( ) ) ;
assertThat ( deserialized )
. containsAllEntriesOf ( map ( "_error_type" , "java.lang.RuntimeException" , "_error_message" , "Boom" ) ) ;