Skip to content

Commit c1322b2

Browse files
committed
feat: add support for metadata auto-population and redirection to STDOUT (#649)
Add configuration to opt-out metadata auto-population. Add configuration to redirect appender output to stdout. Add relevant unit tests. Refactor unit tests. Improve javadoc comments for methods. Remove warnings. Make methods with no modifier to be private.
1 parent 37ca717 commit c1322b2

File tree

4 files changed

+335
-253
lines changed

4 files changed

+335
-253
lines changed

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

Lines changed: 130 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.google.common.collect.ImmutableList;
3939
import java.io.FileInputStream;
4040
import java.io.IOException;
41+
import java.time.Instant;
4142
import java.util.ArrayList;
4243
import java.util.Collections;
4344
import java.util.HashMap;
@@ -58,15 +59,21 @@
5859
* <level>INFO</level>
5960
* </filter>
6061
*
61-
* <!-- Optional: defaults to "java.log" -->
62+
* <!-- Optional: defaults to {@code "java.log"} -->
6263
* <log>application.log</log>
6364
*
64-
* <!-- Optional: defaults to "ERROR" -->
65+
* <!-- Optional: defaults to {@code "ERROR"} -->
6566
* <flushLevel>WARNING</flushLevel>
6667
*
67-
* <!-- Optional: defaults to ASYNC -->
68+
* <!-- Optional: defaults to {@code ASYNC} -->
6869
* <writeSynchronicity>SYNC</writeSynchronicity>
6970
*
71+
* <!-- Optional: defaults to {@code true} -->
72+
* <autoPopulateMetadata>false</autoPopulateMetadata>
73+
*
74+
* <!-- Optional: defaults to {@code false} -->
75+
* <redirectToStdout>true</redirectToStdout>
76+
*
7077
* &lt;!-- Optional: auto detects on App Engine Flex, Standard, GCE and GKE, defaults to "global". See <a
7178
* href=
7279
* "https://cloud.google.com/logging/docs/api/v2/resource-list">supported resource types</a> --&gt;
@@ -96,6 +103,7 @@ public class LoggingAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
96103

97104
private volatile Logging logging;
98105
private LoggingOptions loggingOptions;
106+
private MonitoredResource monitoredResource;
99107
private List<LoggingEnhancer> loggingEnhancers;
100108
private List<LoggingEventEnhancer> loggingEventEnhancers;
101109
private WriteOption[] defaultWriteOptions;
@@ -105,14 +113,16 @@ public class LoggingAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
105113
private String resourceType;
106114
private String credentialsFile;
107115
private String logDestinationProjectId;
116+
private boolean autoPopulateMetadata = true;
117+
private boolean redirectToStdout = false;
108118
private Synchronicity writeSyncFlag = Synchronicity.ASYNC;
109119
private final Set<String> enhancerClassNames = new HashSet<>();
110120
private final Set<String> loggingEventEnhancerClassNames = new HashSet<>();
111121

112122
/**
113-
* Batched logging requests get immediately flushed for logs at or above this level.
123+
* Sets a threshold for log severity level to flush all log entries that were batched so far.
114124
*
115-
* <p>Defaults to Error if not set.
125+
* <p>Defaults to Error.
116126
*
117127
* @param flushLevel Logback log level
118128
*/
@@ -121,35 +131,37 @@ public void setFlushLevel(Level flushLevel) {
121131
}
122132

123133
/**
124-
* Sets the log filename.
134+
* Sets the LOG_ID part of the <a href="log
135+
* name">https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#FIELDS.log_name</a>
136+
* for which the logs are ingested.
125137
*
126-
* @param log filename
138+
* @param log LOG_ID part of the name
127139
*/
128140
public void setLog(String log) {
129141
this.log = log;
130142
}
131143

132144
/**
133-
* Sets the name of the monitored resource (Optional).
134-
*
135-
* <p>Must be a <a href=
136-
* "https://cloud.google.com/logging/docs/api/v2/resource-list">supported</a> resource type.
137-
* gae_app, gce_instance and container are auto-detected.
145+
* Sets the name of the monitored resource (Optional). If not define the appender will try to
146+
* identify the resource type automatically. Currently support resource types include "gae_app",
147+
* "gce_instance", "k8s_container", "cloud_run_revision" and "cloud_function". If the appender
148+
* fails to identify the resource type it will be set to "global".
138149
*
139-
* <p>Defaults to "global"
150+
* <p>Must be a one of the <a href=
151+
* "https://cloud.google.com/logging/docs/api/v2/resource-list">supported</a> resource types.
140152
*
141-
* @param resourceType name of the monitored resource
153+
* @param resourceType the name of the monitored resource.
142154
*/
143155
public void setResourceType(String resourceType) {
144156
this.resourceType = resourceType;
145157
}
146158

147159
/**
148-
* Sets the credentials file to use to create the {@link LoggingOptions}. The credentials returned
149-
* by {@link GoogleCredentials#getApplicationDefault()} will be used if no custom credentials file
150-
* has been set.
160+
* Sets the path to the <a href="credential
161+
* file">https://cloud.google.com/iam/docs/creating-managing-service-account-keys</a>. If not set
162+
* the appender will use {@link GoogleCredentials#getApplicationDefault()} to authenticate.
151163
*
152-
* @param credentialsFile The credentials file to use.
164+
* @param credentialsFile the path to the credentials file.
153165
*/
154166
public void setCredentialsFile(String credentialsFile) {
155167
this.credentialsFile = credentialsFile;
@@ -166,14 +178,39 @@ public void setLogDestinationProjectId(String projectId) {
166178
}
167179

168180
/**
169-
* Define synchronization mode for writing log entries.
181+
* Sets the log ingestion mode. It can be one of the {@link Synchronicity} values.
170182
*
171-
* @param flag to set {@code Synchronicity} value.
183+
* <p>Default to {@code Synchronicity.ASYNC}
184+
*
185+
* @param flag the new ingestion mode.
172186
*/
173187
public void setWriteSynchronicity(Synchronicity flag) {
174188
this.writeSyncFlag = flag;
175189
}
176190

191+
/**
192+
* Sets the automatic population of metadata fields for ingested logs.
193+
*
194+
* <p>Default to {@code true}.
195+
*
196+
* @param flag the metadata auto-population flag.
197+
*/
198+
public void setAutoPopulateMetadata(boolean flag) {
199+
autoPopulateMetadata = flag;
200+
}
201+
202+
/**
203+
* Sets the redirect of the appender's output to STDOUT instead of ingesting logs to Cloud Logging
204+
* using Logging API.
205+
*
206+
* <p>Default to {@code false}.
207+
*
208+
* @param flag the redirect flag.
209+
*/
210+
public void setRedirectToStdout(boolean flag) {
211+
redirectToStdout = flag;
212+
}
213+
177214
/** Add extra labels using classes that implement {@link LoggingEnhancer}. */
178215
public void addEnhancer(String enhancerClassName) {
179216
this.enhancerClassNames.add(enhancerClassName);
@@ -183,57 +220,70 @@ public void addLoggingEventEnhancer(String enhancerClassName) {
183220
this.loggingEventEnhancerClassNames.add(enhancerClassName);
184221
}
185222

186-
Level getFlushLevel() {
187-
return (flushLevel != null) ? flushLevel : Level.ERROR;
223+
/**
224+
* Returns the current value of the ingestion mode.
225+
*
226+
* <p>The method is deprecated. Use appender configuration to setup the ingestion
227+
*
228+
* @return a {@link Synchronicity} value of the ingestion module.
229+
*/
230+
@Deprecated
231+
public Synchronicity getWriteSynchronicity() {
232+
return (this.writeSyncFlag != null) ? this.writeSyncFlag : Synchronicity.ASYNC;
188233
}
189234

190-
String getLogName() {
191-
return (log != null) ? log : "java.log";
235+
private void setupMonitoredResource() {
236+
if (autoPopulateMetadata) {
237+
monitoredResource = MonitoredResourceUtil.getResource(getProjectId(), resourceType);
238+
} else {
239+
monitoredResource = null;
240+
}
192241
}
193242

194-
public Synchronicity getWriteSynchronicity() {
195-
return (this.writeSyncFlag != null) ? this.writeSyncFlag : Synchronicity.ASYNC;
243+
@InternalApi("Visible for testing")
244+
void setupMonitoredResource(MonitoredResource monitoredResource) {
245+
this.monitoredResource = monitoredResource;
196246
}
197247

198-
MonitoredResource getMonitoredResource(String projectId) {
199-
return MonitoredResourceUtil.getResource(projectId, resourceType);
248+
private Level getFlushLevel() {
249+
return (flushLevel != null) ? flushLevel : Level.ERROR;
200250
}
201251

202-
List<LoggingEnhancer> getLoggingEnhancers() {
203-
return getEnhancers(enhancerClassNames);
252+
private String getLogName() {
253+
return (log != null) ? log : "java.log";
204254
}
205255

206-
List<LoggingEventEnhancer> getLoggingEventEnhancers() {
256+
private List<LoggingEnhancer> getLoggingEnhancers() {
257+
return getEnhancers(enhancerClassNames, LoggingEnhancer.class);
258+
}
259+
260+
private List<LoggingEventEnhancer> getLoggingEventEnhancers() {
207261
if (loggingEventEnhancerClassNames.isEmpty()) {
208262
return DEFAULT_LOGGING_EVENT_ENHANCERS;
209263
} else {
210-
return getEnhancers(loggingEventEnhancerClassNames);
264+
return getEnhancers(loggingEventEnhancerClassNames, LoggingEventEnhancer.class);
211265
}
212266
}
213267

214-
<T> List<T> getEnhancers(Set<String> classNames) {
215-
List<T> loggingEnhancers = new ArrayList<>();
268+
private <T> List<T> getEnhancers(Set<String> classNames, Class<T> classOfT) {
269+
List<T> enhancers = new ArrayList<>();
216270
if (classNames != null) {
217-
for (String enhancerClassName : classNames) {
218-
if (enhancerClassName != null) {
219-
T enhancer = getEnhancer(enhancerClassName);
220-
if (enhancer != null) {
221-
loggingEnhancers.add(enhancer);
271+
for (String className : classNames) {
272+
if (className != null) {
273+
try {
274+
T enhancer =
275+
Loader.loadClass(className.trim())
276+
.asSubclass(classOfT)
277+
.getDeclaredConstructor()
278+
.newInstance();
279+
enhancers.add(enhancer);
280+
} catch (Exception ex) {
281+
// invalid className: ignore
222282
}
223283
}
224284
}
225285
}
226-
return loggingEnhancers;
227-
}
228-
229-
private <T> T getEnhancer(String enhancerClassName) {
230-
try {
231-
Class<T> clz = (Class<T>) Loader.loadClass(enhancerClassName.trim());
232-
return clz.getDeclaredConstructor().newInstance();
233-
} catch (Exception ex) {
234-
// If we cannot create the enhancer we fallback to null
235-
}
236-
return null;
286+
return enhancers;
237287
}
238288

239289
/** Initialize and configure the cloud logging service. */
@@ -242,9 +292,13 @@ public synchronized void start() {
242292
if (isStarted()) {
243293
return;
244294
}
245-
MonitoredResource resource = getMonitoredResource(getProjectId());
295+
296+
setupMonitoredResource();
297+
246298
defaultWriteOptions =
247-
new WriteOption[] {WriteOption.logName(getLogName()), WriteOption.resource(resource)};
299+
new WriteOption[] {
300+
WriteOption.logName(getLogName()), WriteOption.resource(monitoredResource)
301+
};
248302
Level flushLevel = getFlushLevel();
249303
if (flushLevel != Level.OFF) {
250304
getLogging().setFlushSeverity(severityFor(flushLevel));
@@ -265,8 +319,26 @@ String getProjectId() {
265319

266320
@Override
267321
protected void append(ILoggingEvent e) {
268-
LogEntry logEntry = logEntryFor(e);
269-
getLogging().write(Collections.singleton(logEntry), defaultWriteOptions);
322+
Iterable<LogEntry> entries = Collections.singleton(logEntryFor(e));
323+
if (autoPopulateMetadata) {
324+
entries =
325+
getLogging()
326+
.populateMetadata(
327+
entries,
328+
monitoredResource,
329+
"com.google.cloud.logging",
330+
"jdk",
331+
"sun",
332+
"java",
333+
"ch.qos.logback");
334+
}
335+
if (redirectToStdout) {
336+
for (LogEntry entry : entries) {
337+
System.out.println(entry.toStructuredJsonString());
338+
}
339+
} else {
340+
getLogging().write(entries, defaultWriteOptions);
341+
}
270342
}
271343

272344
@Override
@@ -295,6 +367,7 @@ Logging getLogging() {
295367
}
296368

297369
/** Flushes any pending asynchronous logging writes. */
370+
@Deprecated
298371
public void flush() {
299372
if (!isStarted()) {
300373
return;
@@ -321,6 +394,8 @@ protected LoggingOptions getLoggingOptions() {
321394
e);
322395
}
323396
}
397+
// opt-out metadata auto-population to control it in the appender code
398+
builder.setAutoPopulateMetadata(false);
324399
loggingOptions = builder.build();
325400
}
326401
return loggingOptions;
@@ -340,7 +415,7 @@ private LogEntry logEntryFor(ILoggingEvent e) {
340415
}
341416
LogEntry.Builder builder =
342417
LogEntry.newBuilder(Payload.JsonPayload.of(jsonContent))
343-
.setTimestamp(e.getTimeStamp())
418+
.setTimestamp(Instant.ofEpochMilli(e.getTimeStamp()))
344419
.setSeverity(severity);
345420
builder
346421
.addLabel(LEVEL_NAME_KEY, level.toString())

src/test/java/com/example/enhancers/AnotherTestLoggingEnhancer.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/test/java/com/example/enhancers/TestLoggingEnhancer.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)