diff --git a/.readme-partials.yaml b/.readme-partials.yaml index 95e214c54..557361955 100644 --- a/.readme-partials.yaml +++ b/.readme-partials.yaml @@ -134,3 +134,62 @@ custom_content: | ``` com.google.cloud.examples.logging.snippets.AddLoggingHandler.handlers=com.google.cloud.logging.LoggingHandler ``` + + #### Alternative way to ingest logs in Google Cloud managed environments + + If you use Java logger with the Cloud Logging Handler, you can configure the handler to output logs to `stdout` using + the [structured logging Json format](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields). + To do this, add `com.google.cloud.logging.LoggingHandler.redirectToStdout=true` to the logger configuration file. + You can use this configuration when running applications in Google Cloud managed environments such as AppEngine, Cloud Run, + Cloud Function or GKE. The logger agent installed on these environments can capture STDOUT and ingest it into Cloud Logging. + The agent can parse structured logs printed to STDOUT and capture additional log metadata beside the log payload. + The parsed information includes severity, source location, user labels, http request and tracing information. + + #### Auto-population of log entrys' metadata + + LogEntry object metadata information such as [monitored resource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource), + [Http request](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#HttpRequest) or + [source location](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntrySourceLocation) + are automatically populated with information that the library retrieves from the execution context. + The library populates only empty (set to `null`) LogEntry fields. + This behavior in the `Logging` instance can be opted out via `LoggingOptions`. + Call `LoggingOptions.Builder.setAutoPopulateMetadata(false)` to configure logging options to opt-out the metadata auto-population. + Cloud Logging handler can be configured to opt-out automatic population of the metadata using the logger configuration. + To disable the metadata auto-population add `com.google.cloud.logging.LoggingHandler.autoPopulateMetadata=false` + to the logger configuration file. + + The auto-population logic populates source location _only_ for log entries with `Severity.DEBUG` severity. + The execution context of the Http request and tracing information is maintained by `ContextHandler` class. + The context is managed in the scope of the thread. + If you do not use thread pools for multi-threading the `ContextHandler` can be configured to propagate the context + to the scope of the child threads. + To enable this add `com.google.cloud.logging.ContextHandler.useInheritedContext=true` to the logger configuration file. + The library provides two methods to update the context: + + * Manually set the context. You can use the following methods of the `Context.Builder` to set the context information. + Use the method `setRequest()` to setup the `HttpRequest` instance or `setRequestUrl()`, `setRequestMethod()`, + `setReferer() `, `setRemoteIp()` and `setServerIp()` to setup the fields of the `HttpRequest`. + The trace and span Ids can be set directly using `setTraceId()` and `setSpanId()` respectively. + Alternatively it can be parsed from the W3C tracing context header using `loadW3CTraceParentContext()` or + from the Google Cloud tracing context header using `loadCloudTraceContext()`. + + ```java + Context context = Context.newBuilder().setHttpRequest(request).setTrace(traceId).setSpanId(spanId).build(); + (new ContextHandler()).setCurrentContext(context); + ``` + + * Using [servlet initializer](https://github.com/googleapis/java-logging-servlet-initializer). + If your application uses a Web server based on Jakarta servlets (e.g. Jetty or Tomcat), you can add the servlet initializer + package to your WAR. The package implements a service provider interface (SPI) for + [javax.servlet.ServletContainerInitializer](https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html) + and filters all servlet requests to automatically capture the execution context of the servlet request and store it using + `ContextHandler` class. The stored `Context` class instances are used to populate Http request and tracing information. + If you use Maven, to use the servlet initializer add the following dependency to your BOM: + + ```xml + + com.google.cloud + google-cloud-logging-servlet-initializer + + ``` + diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java index d53c95306..17c89a7cc 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java @@ -653,7 +653,7 @@ public StructuredLogFormatter appendField(String name, Object value, boolean app checkNotNull(name); if (value != null) { builder.append(gson.toJson(name)).append(":").append(gson.toJson(value)); - if (!appendComma) { + if (appendComma) { builder.append(","); } } @@ -661,7 +661,7 @@ public StructuredLogFormatter appendField(String name, Object value, boolean app } public StructuredLogFormatter appendField(String name, Object value) { - return appendField(name, value, false); + return appendField(name, value, true); } /** @@ -677,7 +677,7 @@ public StructuredLogFormatter appendDict(Map value, boolean appe // append json object without brackets if (json.length() > 1) { builder.append(json.substring(0, json.length() - 1).substring(1)); - if (!appendComma) { + if (appendComma) { builder.append(","); } } @@ -710,10 +710,10 @@ public String toStructuredJsonString() { .appendField("logging.googleapis.com/trace", trace) .appendField("logging.googleapis.com/trace_sampled", traceSampled); if (payload.getType() == Type.STRING) { - formatter.appendField("message", payload.getData(), true); + formatter.appendField("message", payload.getData(), false); } else if (payload.getType() == Type.JSON) { Payload.JsonPayload jsonPayload = (Payload.JsonPayload) payload; - formatter.appendDict(jsonPayload.getDataAsMap(), true); + formatter.appendDict(jsonPayload.getDataAsMap(), false); } builder.append("}"); return builder.toString(); diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java index 0a5cab80c..4404ea675 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java @@ -848,12 +848,12 @@ public void write(Iterable logEntries, WriteOption... options) { try { final Map writeOptions = optionMap(options); - final Boolean populateMetadata1 = getOptions().getAutoPopulateMetadata(); - final Boolean populateMetadata2 = + final Boolean logingOptionsPopulateFlag = getOptions().getAutoPopulateMetadata(); + final Boolean writeOptionPopulateFlga = WriteOption.OptionType.AUTO_POPULATE_METADATA.get(writeOptions); - if (populateMetadata2 == Boolean.TRUE - || (populateMetadata2 == null && populateMetadata1 == Boolean.TRUE)) { + if (writeOptionPopulateFlga == Boolean.TRUE + || (writeOptionPopulateFlga == null && logingOptionsPopulateFlag == Boolean.TRUE)) { final MonitoredResource sharedResourceMetadata = RESOURCE.get(writeOptions); logEntries = populateMetadata(logEntries, sharedResourceMetadata, this.getClass().getName());