Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Commit 3a08dac

Browse files
feat: Add support for library instrumentation (#789)
* feat: Add support for library instrumentation * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Update pom.xml * Address PR comments Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent fcca46b commit 3a08dac

4 files changed

Lines changed: 109 additions & 5 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ If you are using Maven, add this to your pom.xml file:
2222
<dependency>
2323
<groupId>com.google.cloud</groupId>
2424
<artifactId>google-cloud-logging-logback</artifactId>
25-
<version>0.125.2-alpha</version>
25+
<version>0.125.3-alpha</version>
2626
</dependency>
2727
```
2828

2929
If you are using Gradle without BOM, add this to your dependencies
3030

3131
```Groovy
32-
implementation 'com.google.cloud:google-cloud-logging-logback:0.125.2-alpha'
32+
implementation 'com.google.cloud:google-cloud-logging-logback:0.125.3-alpha'
3333
```
3434

3535
If you are using SBT, add this to your dependencies
3636

3737
```Scala
38-
libraryDependencies += "com.google.cloud" % "google-cloud-logging-logback" % "0.125.2-alpha"
38+
libraryDependencies += "com.google.cloud" % "google-cloud-logging-logback" % "0.125.3-alpha"
3939
```
4040

4141
## Authentication

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@
134134
</exclusion>
135135
</exclusions>
136136
</dependency>
137+
<dependency>
138+
<groupId>com.google.protobuf</groupId>
139+
<artifactId>protobuf-java</artifactId>
140+
</dependency>
137141
</dependencies>
138142

139143
<reporting>

src/main/java/com/google/cloud/logging/logback/LoggingAppender.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.api.core.InternalApi;
2626
import com.google.auth.oauth2.GoogleCredentials;
2727
import com.google.cloud.MonitoredResource;
28+
import com.google.cloud.logging.Instrumentation;
2829
import com.google.cloud.logging.LogEntry;
2930
import com.google.cloud.logging.Logging;
3031
import com.google.cloud.logging.Logging.WriteOption;
@@ -40,7 +41,6 @@
4041
import java.io.IOException;
4142
import java.time.Instant;
4243
import java.util.ArrayList;
43-
import java.util.Collections;
4444
import java.util.HashMap;
4545
import java.util.HashSet;
4646
import java.util.List;
@@ -100,6 +100,9 @@ public class LoggingAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
100100
"type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
101101
private static final List<LoggingEventEnhancer> DEFAULT_LOGGING_EVENT_ENHANCERS =
102102
ImmutableList.<LoggingEventEnhancer>of(new MDCEventEnhancer());
103+
public static final String JAVA_LOGBACK_LIBRARY_NAME = "java-logback";
104+
private static boolean instrumentationAdded = false;
105+
private static Object instrumentationLock = new Object();
103106

104107
private volatile Logging logging;
105108
private LoggingOptions loggingOptions;
@@ -317,7 +320,16 @@ String getProjectId() {
317320

318321
@Override
319322
protected void append(ILoggingEvent e) {
320-
Iterable<LogEntry> entries = Collections.singleton(logEntryFor(e));
323+
List<LogEntry> entriesList = new ArrayList<LogEntry>();
324+
entriesList.add(logEntryFor(e));
325+
// Check if instrumentation was already added - if not, create a log entry with instrumentation
326+
// data
327+
if (!setInstrumentationStatus(true)) {
328+
entriesList.add(
329+
Instrumentation.createDiagnosticEntry(
330+
JAVA_LOGBACK_LIBRARY_NAME, Instrumentation.getLibraryVersion(LoggingAppender.class)));
331+
}
332+
Iterable<LogEntry> entries = entriesList;
321333
if (autoPopulateMetadata) {
322334
entries =
323335
getLogging()
@@ -490,4 +502,19 @@ private static Severity severityFor(Level level) {
490502
return Severity.DEFAULT;
491503
}
492504
}
505+
506+
/**
507+
* The package-private helper method used to set the flag which indicates if instrumentation info
508+
* already written or not.
509+
*
510+
* @returns The value of the flag before it was set.
511+
*/
512+
static boolean setInstrumentationStatus(boolean value) {
513+
if (instrumentationAdded == value) return instrumentationAdded;
514+
synchronized (instrumentationLock) {
515+
boolean current = instrumentationAdded;
516+
instrumentationAdded = value;
517+
return current;
518+
}
519+
}
493520
}

src/test/java/com/google/cloud/logging/logback/LoggingAppenderTest.java

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,28 @@
2222
import static org.easymock.EasyMock.expectLastCall;
2323
import static org.easymock.EasyMock.replay;
2424
import static org.easymock.EasyMock.verify;
25+
import static org.junit.Assert.assertEquals;
2526

2627
import ch.qos.logback.classic.Level;
2728
import ch.qos.logback.classic.filter.ThresholdFilter;
2829
import ch.qos.logback.classic.spi.ILoggingEvent;
2930
import ch.qos.logback.classic.spi.LoggingEvent;
3031
import com.google.cloud.MonitoredResource;
3132
import com.google.cloud.Timestamp;
33+
import com.google.cloud.logging.Instrumentation;
3234
import com.google.cloud.logging.LogEntry;
3335
import com.google.cloud.logging.Logging;
3436
import com.google.cloud.logging.Logging.WriteOption;
3537
import com.google.cloud.logging.LoggingEnhancer;
3638
import com.google.cloud.logging.Payload;
39+
import com.google.cloud.logging.Payload.JsonPayload;
40+
import com.google.cloud.logging.Payload.Type;
3741
import com.google.cloud.logging.Severity;
3842
import com.google.common.base.Strings;
3943
import com.google.common.collect.ImmutableList;
4044
import com.google.common.collect.ImmutableMap;
45+
import com.google.protobuf.ListValue;
46+
import com.google.protobuf.Value;
4147
import java.io.ByteArrayOutputStream;
4248
import java.io.PrintStream;
4349
import java.time.Instant;
@@ -139,6 +145,7 @@ Logging getLogging() {
139145

140146
@Before
141147
public void setUp() {
148+
LoggingAppender.setInstrumentationStatus(true);
142149
logging = EasyMock.createStrictMock(Logging.class);
143150
loggingAppender = new TestLoggingAppender();
144151
loggingAppender.setAutoPopulateMetadata(false);
@@ -446,4 +453,70 @@ public void testRedirectToStdoutDisabled() {
446453
assertThat(Strings.isNullOrEmpty(bout.toString())).isTrue();
447454
System.setOut(null);
448455
}
456+
457+
@Test
458+
public void testFDiagnosticInfoAdded() {
459+
LoggingAppender.setInstrumentationStatus(false);
460+
Capture<Iterable<LogEntry>> capturedArgument = Capture.newInstance();
461+
logging.setFlushSeverity(Severity.ERROR);
462+
logging.write(
463+
capture(capturedArgument), anyObject(WriteOption.class), anyObject(WriteOption.class));
464+
replay(logging);
465+
LoggingEvent loggingEvent =
466+
createLoggingEvent(Level.ERROR, Timestamp.ofTimeSecondsAndNanos(100000, 0).getSeconds());
467+
loggingAppender.start();
468+
loggingAppender.doAppend(loggingEvent);
469+
verify(logging);
470+
int count = 0;
471+
int diagnosticRecordCount = 0;
472+
for (LogEntry entry : capturedArgument.getValue()) {
473+
count++;
474+
if (entry.getPayload().getType() == Type.JSON) {
475+
JsonPayload payload = entry.<Payload.JsonPayload>getPayload();
476+
if (!payload.getData().containsFields(Instrumentation.DIAGNOSTIC_INFO_KEY)) continue;
477+
ListValue infoList =
478+
payload
479+
.getData()
480+
.getFieldsOrThrow(Instrumentation.DIAGNOSTIC_INFO_KEY)
481+
.getStructValue()
482+
.getFieldsOrThrow(Instrumentation.INSTRUMENTATION_SOURCE_KEY)
483+
.getListValue();
484+
for (Value val : infoList.getValuesList()) {
485+
String name =
486+
val.getStructValue()
487+
.getFieldsOrThrow(Instrumentation.INSTRUMENTATION_NAME_KEY)
488+
.getStringValue();
489+
assertThat(name.startsWith(Instrumentation.JAVA_LIBRARY_NAME_PREFIX)).isTrue();
490+
if (name.equals(LoggingAppender.JAVA_LOGBACK_LIBRARY_NAME)) {
491+
diagnosticRecordCount++;
492+
}
493+
}
494+
}
495+
}
496+
assertEquals(count, 2);
497+
assertEquals(diagnosticRecordCount, 1);
498+
}
499+
500+
@Test
501+
public void testFDiagnosticInfoNotAdded() {
502+
logging.setFlushSeverity(Severity.ERROR);
503+
Capture<Iterable<LogEntry>> capturedArgument = Capture.newInstance();
504+
logging.write(
505+
capture(capturedArgument), anyObject(WriteOption.class), anyObject(WriteOption.class));
506+
replay(logging);
507+
LoggingEvent loggingEvent =
508+
createLoggingEvent(Level.WARN, Timestamp.ofTimeSecondsAndNanos(100000, 0).getSeconds());
509+
loggingAppender.start();
510+
loggingAppender.doAppend(loggingEvent);
511+
verify(logging);
512+
int count = 0;
513+
for (LogEntry entry : capturedArgument.getValue()) {
514+
count++;
515+
if (entry.getPayload().getType() == Type.JSON) {
516+
JsonPayload payload = entry.<Payload.JsonPayload>getPayload();
517+
assertThat(payload.getData().containsFields(Instrumentation.DIAGNOSTIC_INFO_KEY)).isFalse();
518+
}
519+
}
520+
assertEquals(count, 1);
521+
}
449522
}

0 commit comments

Comments
 (0)