Skip to content

Commit

Permalink
Merge pull request #685 from wabrit/extension-point-for-custom-labels
Browse files Browse the repository at this point in the history
provide an extension mechanism for adding custom labels to gcp logs
  • Loading branch information
loicmathieu authored Oct 7, 2024
2 parents fa7a6c5 + 8cc5ab7 commit 7a5e689
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 1 deletion.
28 changes: 28 additions & 0 deletions docs/modules/ROOT/pages/logging.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,34 @@ By default, this will also be set on the Google Cloud Operations log entry as a

* `quarkus.google.cloud.logging.gcp-tracing.enabled=[true|false]`

=== Custom Labels

In order to include additional labels in the log, you must bind a `LogRecordLabelExtractor` to the CDI context, e.g.:

[source,java]
----
package mypackage;
import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
import org.jboss.logmanager.ExtLogRecord;
import io.quarkiverse.googlecloudservices.logging.runtime.LogRecordLabelExtractor;
@ApplicationScoped
public class LogLabelExtractor implements LogRecordLabelExtractor {
@Override
public Map<String, String> getCustomLabels(ExtLogRecord extLogRecord) {
return Map.of(/* some label, some value */);
}
}
----

This will include additional labels in the log.

== Injecting GCP Logging
You can inject a `com.google.cloud.logging.Logging` instance directly. If you do so, the configuration for the project to use, still apply.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.List;

import io.quarkiverse.googlecloudservices.logging.runtime.LogRecordLabelExtractor;
import io.quarkiverse.googlecloudservices.logging.runtime.LoggingConfiguration;
import io.quarkiverse.googlecloudservices.logging.runtime.TraceInfoExtractor;
import io.quarkiverse.googlecloudservices.logging.runtime.cdi.LoggingProducer;
Expand Down Expand Up @@ -34,7 +35,8 @@ public AdditionalBeanBuildItem logging() {
public UnremovableBeanBuildItem helperClasses() {
return UnremovableBeanBuildItem.beanTypes(
TraceInfoExtractor.class,
LoggingConfiguration.class);
LoggingConfiguration.class,
LogRecordLabelExtractor.class);
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkiverse.googlecloudservices.logging.runtime;

import java.util.Map;

import org.jboss.logmanager.ExtLogRecord;

/**
* Bind an instance of this interface to include additional labels
* in the log record. You should only bind one extractor in the CDI context.
*/
public interface LogRecordLabelExtractor {

/**
* Supply additional labels for a log record.
*
* @param record Record for which labels can be supplied, never null
* @return a map of additional label values, may return null or empty but neither
* keys nor values therein should be null
*/
Map<String, String> getCustomLabels(ExtLogRecord record);

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkiverse.googlecloudservices.logging.runtime;

import java.util.Collections;
import java.util.Map;
import java.util.logging.ErrorManager;

import org.jboss.logmanager.ExtHandler;
Expand Down Expand Up @@ -33,6 +34,7 @@ public class LoggingHandler extends ExtHandler {
private WriteOption[] defaultWriteOptions;
private InternalHandler internalHandler;
private TraceInfoExtractor traceExtractor;
private LogRecordLabelExtractor logRecordLabelExtractor;

public LoggingHandler(LoggingConfiguration config) {
this.config = config;
Expand Down Expand Up @@ -83,6 +85,14 @@ private LogEntry transform(ExtLogRecord record, TraceInfo trace) {
.setSeverity(LevelTransformer.toSeverity(record.getLevel()))
.setTimestamp(record.getInstant());

final Map<String, String> customLabels = logRecordLabelExtractor.getCustomLabels(record);

if (customLabels != null) {
for (Map.Entry<String, String> entry : customLabels.entrySet()) {
builder = builder.addLabel(entry.getKey(), entry.getValue());
}
}

if (this.config.gcpTracing().enabled() && trace != null && !Strings.isNullOrEmpty(trace.getTraceId())) {
builder = builder
.setTrace(composeTraceString(trace.getTraceId()))
Expand Down Expand Up @@ -120,10 +130,21 @@ private synchronized Logging initGetLogging() {
initInternalHandler();
// init trace extractor
initTraceExtractor();
// init log record label extractor
initLogRecordLabelExtractor();
}
return log;
}

private void initLogRecordLabelExtractor() {
InstanceHandle<LogRecordLabelExtractor> handle = Arc.container().instance(LogRecordLabelExtractor.class);
if (handle.isAvailable()) {
this.logRecordLabelExtractor = handle.get();
} else {
this.logRecordLabelExtractor = s -> Collections.emptyMap();
}
}

private void initInternalHandler() {
if (this.config.format() == LogFormat.JSON) {
this.internalHandler = new JsonHandler(this.config, getErrorManager());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Level;
Expand Down Expand Up @@ -79,6 +80,13 @@ void shouldLogToStdoutWithTraceInfoAndLabels() {
() -> assertEquals(spanId, logEntryJson.get("logging.googleapis.com/spanId").getAsString()),
() -> assertTrue(logEntryJson.get("logging.googleapis.com/trace_sampled").getAsBoolean()));

JsonObject labels = logEntryJson.get("logging.googleapis.com/labels").getAsJsonObject();
assertNotNull(labels);
assertAll(
() -> assertEquals(2, labels.entrySet().size()),
() -> assertEquals("value1", labels.get("label1").getAsString()),
() -> assertEquals("value2", labels.get("label2").getAsString()));

JsonObject log = logEntryJson.get("log").getAsJsonObject();
assertNotNull(log);
assertAll(
Expand All @@ -102,6 +110,11 @@ private ArcContainer createArcContainer(String traceId, String spanId) {
when(traceInfoInstanceHandler.isAvailable()).thenReturn(true);
when(container.instance(TraceInfoExtractor.class)).thenReturn(traceInfoInstanceHandler);

InstanceHandle<LogRecordLabelExtractor> logRecordLabelExtractorInstanceHandle = Mockito.mock(InstanceHandle.class);
when(logRecordLabelExtractorInstanceHandle.get()).thenReturn(x -> Map.of("label1", "value1", "label2", "value2"));
when(logRecordLabelExtractorInstanceHandle.isAvailable()).thenReturn(true);
when(container.instance(LogRecordLabelExtractor.class)).thenReturn(logRecordLabelExtractorInstanceHandle);

InstanceHandle<Logging> loggingInstanceHandler = Mockito.mock(InstanceHandle.class);
Logging logging = Mockito.mock(Logging.class);
when(loggingInstanceHandler.get()).thenReturn(logging);
Expand Down

0 comments on commit 7a5e689

Please sign in to comment.