- Notifications
You must be signed in to change notification settings - Fork 31
Open
Labels
api: loggingIssues related to the googleapis/java-logging-logback API.Issues related to the googleapis/java-logging-logback API.priority: p3Desirable enhancement or fix. May not be included in next release.Desirable enhancement or fix. May not be included in next release.
Description
Is your feature request related to a problem? Please describe.
I would like for structured logging with slf4j facade to have first class support.
Describe the solution you'd like
I would like code like this to be able to send jsonPayload with the fields orderId, amount, etc...
package com.example; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import net.logstash.logback.argument.StructuredArguments; import net.logstash.logback.marker.Markers; import java.util.HashMap; import java.util.Map; /** Sample REST Controller to demonstrate Stackdriver Logging. */ @RestController public class ExampleController { private static final Logger logger = LoggerFactory.getLogger(ExampleController.class); @GetMapping("/log") public String log() { // Method 1: Using StructuredArguments logger.info("Order processed", StructuredArguments.kv("orderId", "12345"), StructuredArguments.kv("amount", 99.99), StructuredArguments.kv("currency", "USD") ); // Method 2: Using Markers for JSON fields Map<String, Object> fields = new HashMap<>(); fields.put("customerId", "CUS-789"); fields.put("region", "US-WEST"); fields.put("priority", 1); logger.info(Markers.appendFields(fields), "Customer order details logged"); return "Structured logs written successfully"; } }Describe alternatives you've considered
I have written a helper class to work-around this but it makes my applications not be able to use the slf4j.Logger interface.
package util import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.slf4j.event.Level; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; /** * Thread-safe structured logging utility that integrates with Google Cloud Logging. * Provides a builder pattern API for constructing log entries with MDC context. * * @author Luke Mauldin (luke.mauldin@kidstrong.com) - KidStrong, Inc. */ public class StructuredLogger { private final Logger logger; /** * Creates a new StructuredLogger instance for the specified class. * * @param clazz The class to create the logger for * @throws IllegalArgumentException if clazz is null */ public StructuredLogger(@NonNull Class<?> clazz) { Objects.requireNonNull(clazz, "Class cannot be null"); this.logger = LoggerFactory.getLogger(clazz); } /** * Creates a new log entry builder. * * @return A new LogBuilder instance */ public LogBuilder log() { return new LogBuilder(); } /** * Builder class for constructing structured log entries. */ public class LogBuilder { private final Map<String, String> fields; private Level level; private String message; private Throwable throwable; private LogBuilder() { this.fields = new LinkedHashMap<>(); this.level = Level.INFO; } /** * Sets the log level to INFO. * * @return this builder */ public LogBuilder info() { this.level = Level.INFO; return this; } /** * Sets the log level to ERROR. * * @return this builder */ public LogBuilder error() { this.level = Level.ERROR; return this; } /** * Sets the log level to WARN. * * @return this builder */ public LogBuilder warn() { this.level = Level.WARN; return this; } /** * Sets the log level to DEBUG. * * @return this builder */ public LogBuilder debug() { this.level = Level.DEBUG; return this; } /** * Adds a field to the log entry. * * @param key The field key * @param value The field value * @return this builder * @throws IllegalArgumentException if key is null */ public LogBuilder addField(@NonNull String key, @Nullable Object value) { Objects.requireNonNull(key, "Field key cannot be null"); fields.put(key, value != null ? value.toString() : "null"); return this; } /** * Adds multiple fields to the log entry. * * @param fields Map of fields to add * @return this builder * @throws IllegalArgumentException if fields is null */ public LogBuilder addFields(@NonNull Map<String, Object> fields) { Objects.requireNonNull(fields, "Fields map cannot be null"); fields.forEach(this::addField); return this; } /** * Sets the log message. * * @param message The log message * @return this builder */ public LogBuilder message(@Nullable String message) { this.message = message; return this; } /** * Adds an exception to the log entry. * * @param throwable The exception to log * @return this builder */ public LogBuilder exception(@Nullable Throwable throwable) { this.throwable = throwable; if (throwable != null) { addField("error_type", throwable.getClass().getName()); addField("error_message", throwable.getMessage()); } return this; } /** * Builds and writes the log entry. */ public void write() { Map<String, String> oldContext = null; try { // Backup existing MDC context oldContext = MDC.getCopyOfContextMap(); // Add all fields to MDC fields.forEach(MDC::put); // Log at the appropriate level switch (level) { case ERROR -> logger.error(message, throwable); case WARN -> logger.warn(message, throwable); case DEBUG -> logger.debug(message, throwable); default -> logger.info(message); } } finally { // Clean up MDC fields.keySet().forEach(MDC::remove); // Restore original MDC context if (oldContext != null) { MDC.setContextMap(oldContext); } else { MDC.clear(); } } } } }schielek and erhan-talarian
Metadata
Metadata
Assignees
Labels
api: loggingIssues related to the googleapis/java-logging-logback API.Issues related to the googleapis/java-logging-logback API.priority: p3Desirable enhancement or fix. May not be included in next release.Desirable enhancement or fix. May not be included in next release.