3838import com .google .common .collect .ImmutableList ;
3939import java .io .FileInputStream ;
4040import java .io .IOException ;
41+ import java .time .Instant ;
4142import java .util .ArrayList ;
4243import java .util .Collections ;
4344import java .util .HashMap ;
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 * <!-- 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> -->
@@ -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 ())
0 commit comments