Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom logging provider to support structured logging for SLF4J 1.x + Logback 1.2.x #3693

Open
wants to merge 65 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
60ec4e6
feat: add sdk logging helpers
JoeWang1127 Mar 12, 2025
88097e8
refactor
JoeWang1127 Mar 12, 2025
bd30ffa
add test deps
JoeWang1127 Mar 12, 2025
b32e11b
add test
JoeWang1127 Mar 12, 2025
4181927
add formatter
JoeWang1127 Mar 12, 2025
98e4130
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 12, 2025
209768b
format
JoeWang1127 Mar 12, 2025
b0310a4
change version def
JoeWang1127 Mar 12, 2025
64acdfb
update test
JoeWang1127 Mar 12, 2025
82ff79d
add profile
JoeWang1127 Mar 12, 2025
e0f8981
skip normal release
JoeWang1127 Mar 12, 2025
204fb8f
add test
JoeWang1127 Mar 12, 2025
925f04c
update test
JoeWang1127 Mar 12, 2025
469c348
update test
JoeWang1127 Mar 12, 2025
25a6cb3
format
JoeWang1127 Mar 12, 2025
d1698a1
add test
JoeWang1127 Mar 13, 2025
b4effd9
remove slf4j deps
JoeWang1127 Mar 13, 2025
fb42331
add parent
JoeWang1127 Mar 13, 2025
d0712cc
update pom
JoeWang1127 Mar 13, 2025
e8eda21
add skip release in parent pom
JoeWang1127 Mar 13, 2025
b6819d8
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 13, 2025
f35a134
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 14, 2025
25705f1
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 14, 2025
0bc9c94
add a bom
JoeWang1127 Mar 14, 2025
710705b
add a empty class for test prep
JoeWang1127 Mar 14, 2025
d56cf81
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 14, 2025
310e546
chore: generate libraries at Fri Mar 14 19:00:09 UTC 2025
cloud-java-bot Mar 14, 2025
8bfd02a
remove optional
JoeWang1127 Mar 14, 2025
7c8e653
add showcase tests
JoeWang1127 Mar 14, 2025
99bf64b
chore: generate libraries at Fri Mar 14 23:07:02 UTC 2025
cloud-java-bot Mar 14, 2025
dc73a51
remove child version
JoeWang1127 Mar 14, 2025
c029d4f
update showcase tests
JoeWang1127 Mar 15, 2025
d3a8988
exclude compile appender in 1.x
JoeWang1127 Mar 15, 2025
9fd9d56
exclude compile appender in 1.x
JoeWang1127 Mar 15, 2025
68c7a3c
exclude compile appender in 1.x
JoeWang1127 Mar 15, 2025
9e69796
add unit tests
JoeWang1127 Mar 15, 2025
4b88289
add early return
JoeWang1127 Mar 15, 2025
4de3bfc
refactor
JoeWang1127 Mar 15, 2025
ed28cd4
update tests
JoeWang1127 Mar 15, 2025
7529de0
rename
JoeWang1127 Mar 15, 2025
f295db8
chore: generate libraries at Sat Mar 15 15:30:16 UTC 2025
cloud-java-bot Mar 15, 2025
f54db56
add showcase test
JoeWang1127 Mar 15, 2025
e717447
add showcase test
JoeWang1127 Mar 15, 2025
8237028
refactor test
JoeWang1127 Mar 15, 2025
4aef523
format
JoeWang1127 Mar 15, 2025
f5efa2a
skip release
JoeWang1127 Mar 15, 2025
c26da4b
format
JoeWang1127 Mar 15, 2025
afb6ae6
merge main branch
JoeWang1127 Mar 18, 2025
12ea137
update root pom
JoeWang1127 Mar 18, 2025
cfc9af3
install logging module in showcase ci
JoeWang1127 Mar 18, 2025
9ea01fa
continue
JoeWang1127 Mar 18, 2025
89c469a
add unit tests
JoeWang1127 Mar 18, 2025
3179277
add included key
JoeWang1127 Mar 18, 2025
39f94ed
add unit tests
JoeWang1127 Mar 18, 2025
1c6f064
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 18, 2025
b8db96f
format
JoeWang1127 Mar 18, 2025
6ed523e
add unit tests
JoeWang1127 Mar 18, 2025
6eb0f8e
change group id in showcase
JoeWang1127 Mar 18, 2025
2cc02e5
add key replacement
JoeWang1127 Mar 18, 2025
05fcd82
run test java 8
JoeWang1127 Mar 18, 2025
179ecc8
final
JoeWang1127 Mar 18, 2025
47b4221
Merge branch 'main' into feat/sdk-logging-helper
JoeWang1127 Mar 19, 2025
eea89ae
change package name
JoeWang1127 Mar 19, 2025
af93306
refactor
JoeWang1127 Mar 19, 2025
5898bd1
add comment
JoeWang1127 Mar 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2025 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.api.logging;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Map;
import net.logstash.logback.composite.loggingevent.MdcJsonProvider;

public class SDKLoggingMdcJsonProvider extends MdcJsonProvider {

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
Map<String, String> mdcProperties = event.getMDCPropertyMap();
if (mdcProperties == null || mdcProperties.isEmpty()) {
return;
}

boolean hasWrittenStart = false;
for (Map.Entry<String, String> entry : mdcProperties.entrySet()) {
String fieldName = entry.getKey();
String entryValueString = entry.getValue();
if (fieldName == null || entryValueString == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logstash-logback has some customization capabilities that I suspect are fairly easy to restore here, like configure specific entries in the MDC to be included or excluded and renaming keys, ref: readme:mdc-fields. It might be as easy as adding these conditions here, or a bit more involved. Can you take a look? I think it nice to have it preserved.
https://github.com/logfellow/logstash-logback-encoder/blob/76f5295f35bc44518af91bfccb07a8ecb142229c/src/main/java/net/logstash/logback/composite/loggingevent/MdcJsonProvider.java#L102-L103

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these features.

return;
}

if (!hasWrittenStart && getFieldName() != null) {
generator.writeObjectFieldStart(getFieldName());
hasWrittenStart = true;
}
generator.writeFieldName(fieldName);

try {
generator.writeTree(convertToTreeNode(entryValueString));
} catch (JsonProcessingException e) {
// in case of conversion exception, just use String
generator.writeObject(entryValueString);
}
}
if (hasWrittenStart) {
generator.writeEndObject();
}
}

private JsonNode convertToTreeNode(String jsonString) throws JsonProcessingException {
return objectMapper.readTree(jsonString);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2025 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.api.logging;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;

class SDKLoggingMdcJsonProviderTest {

private SDKLoggingMdcJsonProvider provider = new SDKLoggingMdcJsonProvider();
private JsonGenerator generator = mock(JsonGenerator.class);
private ILoggingEvent event = mock(ILoggingEvent.class);
private Map<String, String> mdc;

@BeforeEach
void init() {
mdc = new HashMap<>();
when(event.getMDCPropertyMap()).thenReturn(mdc);
}

@AfterEach
void post() {
mdc.clear();
}

@Test
void testWriteNullMdcMap() throws IOException {
when(event.getMDCPropertyMap()).thenReturn(null);
provider.writeTo(generator, event);
verify(generator, never()).writeFieldName(anyString());
verify(generator, never()).writeTree(any(JsonNode.class));
}

@Test
void testWriteEmptyMdcMap() throws IOException {
provider.writeTo(generator, event);
verify(generator, never()).writeFieldName(anyString());
verify(generator, never()).writeTree(any(JsonNode.class));
}

@Test
void testWriteValidJsonStringToJsonTree() throws IOException {
mdc.put(
"json1",
"{\n"
+ " \"@version\": \"1\",\n"
+ " \"textPayload\": \"Received response\",\n"
+ " \"response.payload\": {\n"
+ " \"name\": \"example\",\n"
+ " \"state\": \"ACTIVE\"\n"
+ " }\n"
+ "}");

provider.setFieldName("log-name");
provider.writeTo(generator, event);
InOrder inOrder = inOrder(generator);
inOrder.verify(generator).writeObjectFieldStart("log-name");
inOrder.verify(generator).writeFieldName("json1");
inOrder.verify(generator).writeTree(any(JsonNode.class));
inOrder.verify(generator).writeEndObject();
}

@Test
void testWriteInvalidJsonStringToString() throws IOException {
mdc.put(
"json1",
"{\n"
+ " \"@version\": \"1\",\n"
+ " \"textPayload\": \"Received response\",\n"
+ " \"response.payload\": {\n"
+ " \"name\": \"example\",\n"
+ " \"state\": \"ACTIVE\",\n" // the last semicolon is redundant.
+ " }\n"
+ "}");
provider.writeTo(generator, event);
verify(generator).writeFieldName("json1");
verify(generator).writeObject(anyString());
// should not write tree node because the json string is invalid.
verify(generator, never()).writeTree(any(JsonNode.class));
}

@Test
void testWriteNullValueDoesNotThrowsException() throws IOException {
mdc.put("json1", null);
provider.writeTo(generator, event);
verify(generator, never()).writeObject(anyString());
verify(generator, never()).writeTree(any(JsonNode.class));
}

@Test
void testWriteNullKeyDoesNotThrowsException() throws IOException {
mdc.put(null, "example value");
provider.writeTo(generator, event);
verify(generator, never()).writeObject(anyString());
verify(generator, never()).writeTree(any(JsonNode.class));
}
}
10 changes: 10 additions & 0 deletions java-showcase/gapic-showcase/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@
<testExclude>**/com/google/showcase/v1beta1/it/*.java</testExclude>
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLoggingDisabled.java</testExclude>
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLogging1x.java</testExclude>
<!-- this profile doesn't have logstash deps -->
<testExclude>**/com/google/showcase/v1beta1/it/logging/TestMdcAppender.java</testExclude>
</testExcludes>
</configuration>
</plugin>
Expand Down Expand Up @@ -291,6 +293,12 @@
<version>${slf4j1-logback.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>logback-extension</artifactId>
<version>0.1.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down Expand Up @@ -347,6 +355,8 @@
<testExclude>**/com/google/showcase/v1beta1/it/*.java</testExclude>
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLogging1x.java</testExclude>
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLogging.java</testExclude>
<!-- this profile doesn't have logstash deps -->
<testExclude>**/com/google/showcase/v1beta1/it/logging/TestMdcAppender.java</testExclude>
</testExcludes>
</configuration>
</plugin>
Expand Down
Loading
Loading