googleapis / googleapis/google-cloud-java

[java-logging-logback] Structured Logging first class support

Đang mở
#12,007 5 bình luận 2 reaction 1 người được giao Được @cindy-peng nhận Xem trên GitHub
api: logging priority: p3
Ngôn ngữ chính
Java
Star
2.1k
Fork
1.2k
Merge trung bình
1 ngày 23 giờ
Pull request đã merge (30 ngày)
154

Mô tả

**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...
```java
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 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.
```java
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 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 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 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();
}
}
}
}
}
```

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.