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> -->
@@ -93,6 +100,7 @@ public class LoggingAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
93100
94101 private volatile Logging logging ;
95102 private LoggingOptions loggingOptions ;
103+ private MonitoredResource monitoredResource ;
96104 private List <LoggingEnhancer > loggingEnhancers ;
97105 private List <LoggingEventEnhancer > loggingEventEnhancers ;
98106 private WriteOption [] defaultWriteOptions ;
@@ -101,14 +109,16 @@ public class LoggingAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
101109 private String log ;
102110 private String resourceType ;
103111 private String credentialsFile ;
112+ private boolean autoPopulateMetadata = true ;
113+ private boolean redirectToStdout = false ;
104114 private Synchronicity writeSyncFlag = Synchronicity .ASYNC ;
105115 private final Set <String > enhancerClassNames = new HashSet <>();
106116 private final Set <String > loggingEventEnhancerClassNames = new HashSet <>();
107117
108118 /**
109- * Batched logging requests get immediately flushed for logs at or above this level .
119+ * Sets a threshold for log severity level to flush all log entries that were batched so far .
110120 *
111- * <p>Defaults to Error if not set .
121+ * <p>Defaults to Error.
112122 *
113123 * @param flushLevel Logback log level
114124 */
@@ -117,49 +127,76 @@ public void setFlushLevel(Level flushLevel) {
117127 }
118128
119129 /**
120- * Sets the log filename.
130+ * Sets the LOG_ID part of the <a href="log
131+ * name">https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#FIELDS.log_name</a>
132+ * for which the logs are ingested.
121133 *
122- * @param log filename
134+ * @param log LOG_ID part of the name
123135 */
124136 public void setLog (String log ) {
125137 this .log = log ;
126138 }
127139
128140 /**
129- * Sets the name of the monitored resource (Optional).
130- *
131- * <p>Must be a <a href=
132- * "https://cloud.google.com/logging/docs/api/v2/resource-list">supported</a> resource type.
133- * gae_app, gce_instance and container are auto-detected.
141+ * Sets the name of the monitored resource (Optional). If not define the appender will try to
142+ * identify the resource type automatically. Currently support resource types include "gae_app",
143+ * "gce_instance", "k8s_container", "cloud_run_revision" and "cloud_function". If the appender
144+ * fails to identify the resource type it will be set to "global".
134145 *
135- * <p>Defaults to "global"
146+ * <p>Must be a one of the <a href=
147+ * "https://cloud.google.com/logging/docs/api/v2/resource-list">supported</a> resource types.
136148 *
137- * @param resourceType name of the monitored resource
149+ * @param resourceType the name of the monitored resource.
138150 */
139151 public void setResourceType (String resourceType ) {
140152 this .resourceType = resourceType ;
141153 }
142154
143155 /**
144- * Sets the credentials file to use to create the {@link LoggingOptions}. The credentials returned
145- * by {@link GoogleCredentials#getApplicationDefault()} will be used if no custom credentials file
146- * has been set .
156+ * Sets the path to the <a href="credential
157+ * file">https://cloud.google.com/iam/docs/creating-managing-service-account-keys</a>. If not set
158+ * the appender will use {@link GoogleCredentials#getApplicationDefault()} to authenticate .
147159 *
148- * @param credentialsFile The credentials file to use .
160+ * @param credentialsFile the path to the credentials file .
149161 */
150162 public void setCredentialsFile (String credentialsFile ) {
151163 this .credentialsFile = credentialsFile ;
152164 }
153165
154166 /**
155- * Define synchronization mode for writing log entries .
167+ * Sets the log ingestion mode. It can be one of the {@link Synchronicity} values .
156168 *
157- * @param flag to set {@code Synchronicity} value.
169+ * <p>Default to {@code Synchronicity.ASYNC}
170+ *
171+ * @param flag the new ingestion mode.
158172 */
159173 public void setWriteSynchronicity (Synchronicity flag ) {
160174 this .writeSyncFlag = flag ;
161175 }
162176
177+ /**
178+ * Sets the automatic population of metadata fields for ingested logs.
179+ *
180+ * <p>Default to {@code true}.
181+ *
182+ * @param flag the metadata auto-population flag.
183+ */
184+ public void setAutoPopulateMetadata (boolean flag ) {
185+ autoPopulateMetadata = flag ;
186+ }
187+
188+ /**
189+ * Sets the redirect of the appender's output to STDOUT instead of ingesting logs to Cloud Logging
190+ * using Logging API.
191+ *
192+ * <p>Default to {@code false}.
193+ *
194+ * @param flag the redirect flag.
195+ */
196+ public void setRedirectToStdout (boolean flag ) {
197+ redirectToStdout = flag ;
198+ }
199+
163200 /** Add extra labels using classes that implement {@link LoggingEnhancer}. */
164201 public void addEnhancer (String enhancerClassName ) {
165202 this .enhancerClassNames .add (enhancerClassName );
@@ -169,57 +206,70 @@ public void addLoggingEventEnhancer(String enhancerClassName) {
169206 this .loggingEventEnhancerClassNames .add (enhancerClassName );
170207 }
171208
172- Level getFlushLevel () {
173- return (flushLevel != null ) ? flushLevel : Level .ERROR ;
209+ /**
210+ * Returns the current value of the ingestion mode.
211+ *
212+ * <p>The method is deprecated. Use appender configuration to setup the ingestion
213+ *
214+ * @return a {@link Synchronicity} value of the ingestion module.
215+ */
216+ @ Deprecated
217+ public Synchronicity getWriteSynchronicity () {
218+ return (this .writeSyncFlag != null ) ? this .writeSyncFlag : Synchronicity .ASYNC ;
174219 }
175220
176- String getLogName () {
177- return (log != null ) ? log : "java.log" ;
221+ private void setupMonitoredResource () {
222+ if (autoPopulateMetadata ) {
223+ monitoredResource = MonitoredResourceUtil .getResource (getProjectId (), resourceType );
224+ } else {
225+ monitoredResource = null ;
226+ }
178227 }
179228
180- public Synchronicity getWriteSynchronicity () {
181- return (this .writeSyncFlag != null ) ? this .writeSyncFlag : Synchronicity .ASYNC ;
229+ @ InternalApi ("Visible for testing" )
230+ void setupMonitoredResource (MonitoredResource monitoredResource ) {
231+ this .monitoredResource = monitoredResource ;
182232 }
183233
184- MonitoredResource getMonitoredResource ( String projectId ) {
185- return MonitoredResourceUtil . getResource ( projectId , resourceType ) ;
234+ private Level getFlushLevel ( ) {
235+ return ( flushLevel != null ) ? flushLevel : Level . ERROR ;
186236 }
187237
188- List < LoggingEnhancer > getLoggingEnhancers () {
189- return getEnhancers ( enhancerClassNames ) ;
238+ private String getLogName () {
239+ return ( log != null ) ? log : "java.log" ;
190240 }
191241
192- List <LoggingEventEnhancer > getLoggingEventEnhancers () {
242+ private List <LoggingEnhancer > getLoggingEnhancers () {
243+ return getEnhancers (enhancerClassNames , LoggingEnhancer .class );
244+ }
245+
246+ private List <LoggingEventEnhancer > getLoggingEventEnhancers () {
193247 if (loggingEventEnhancerClassNames .isEmpty ()) {
194248 return DEFAULT_LOGGING_EVENT_ENHANCERS ;
195249 } else {
196- return getEnhancers (loggingEventEnhancerClassNames );
250+ return getEnhancers (loggingEventEnhancerClassNames , LoggingEventEnhancer . class );
197251 }
198252 }
199253
200- <T > List <T > getEnhancers (Set <String > classNames ) {
201- List <T > loggingEnhancers = new ArrayList <>();
254+ private <T > List <T > getEnhancers (Set <String > classNames , Class < T > classOfT ) {
255+ List <T > enhancers = new ArrayList <>();
202256 if (classNames != null ) {
203- for (String enhancerClassName : classNames ) {
204- if (enhancerClassName != null ) {
205- T enhancer = getEnhancer (enhancerClassName );
206- if (enhancer != null ) {
207- loggingEnhancers .add (enhancer );
257+ for (String className : classNames ) {
258+ if (className != null ) {
259+ try {
260+ T enhancer =
261+ Loader .loadClass (className .trim ())
262+ .asSubclass (classOfT )
263+ .getDeclaredConstructor ()
264+ .newInstance ();
265+ enhancers .add (enhancer );
266+ } catch (Exception ex ) {
267+ // invalid className: ignore
208268 }
209269 }
210270 }
211271 }
212- return loggingEnhancers ;
213- }
214-
215- private <T > T getEnhancer (String enhancerClassName ) {
216- try {
217- Class <T > clz = (Class <T >) Loader .loadClass (enhancerClassName .trim ());
218- return clz .getDeclaredConstructor ().newInstance ();
219- } catch (Exception ex ) {
220- // If we cannot create the enhancer we fallback to null
221- }
222- return null ;
272+ return enhancers ;
223273 }
224274
225275 /** Initialize and configure the cloud logging service. */
@@ -228,9 +278,13 @@ public synchronized void start() {
228278 if (isStarted ()) {
229279 return ;
230280 }
231- MonitoredResource resource = getMonitoredResource (getProjectId ());
281+
282+ setupMonitoredResource ();
283+
232284 defaultWriteOptions =
233- new WriteOption [] {WriteOption .logName (getLogName ()), WriteOption .resource (resource )};
285+ new WriteOption [] {
286+ WriteOption .logName (getLogName ()), WriteOption .resource (monitoredResource )
287+ };
234288 Level flushLevel = getFlushLevel ();
235289 if (flushLevel != Level .OFF ) {
236290 getLogging ().setFlushSeverity (severityFor (flushLevel ));
@@ -251,8 +305,26 @@ String getProjectId() {
251305
252306 @ Override
253307 protected void append (ILoggingEvent e ) {
254- LogEntry logEntry = logEntryFor (e );
255- getLogging ().write (Collections .singleton (logEntry ), defaultWriteOptions );
308+ Iterable <LogEntry > entries = Collections .singleton (logEntryFor (e ));
309+ if (autoPopulateMetadata ) {
310+ entries =
311+ getLogging ()
312+ .populateMetadata (
313+ entries ,
314+ monitoredResource ,
315+ "com.google.cloud.logging" ,
316+ "jdk" ,
317+ "sun" ,
318+ "java" ,
319+ "ch.qos.logback" );
320+ }
321+ if (redirectToStdout ) {
322+ for (LogEntry entry : entries ) {
323+ System .out .println (entry .toStructuredJsonString ());
324+ }
325+ } else {
326+ getLogging ().write (entries , defaultWriteOptions );
327+ }
256328 }
257329
258330 @ Override
@@ -281,6 +353,7 @@ Logging getLogging() {
281353 }
282354
283355 /** Flushes any pending asynchronous logging writes. */
356+ @ Deprecated
284357 public void flush () {
285358 if (!isStarted ()) {
286359 return ;
@@ -293,15 +366,11 @@ public void flush() {
293366 /** Gets the {@link LoggingOptions} to use for this {@link LoggingAppender}. */
294367 protected LoggingOptions getLoggingOptions () {
295368 if (loggingOptions == null ) {
296- if (Strings .isNullOrEmpty (credentialsFile )) {
297- loggingOptions = LoggingOptions .getDefaultInstance ();
298- } else {
369+ LoggingOptions .Builder builder = LoggingOptions .newBuilder ();
370+ if (!Strings .isNullOrEmpty (credentialsFile )) {
299371 try {
300- loggingOptions =
301- LoggingOptions .newBuilder ()
302- .setCredentials (
303- GoogleCredentials .fromStream (new FileInputStream (credentialsFile )))
304- .build ();
372+ builder .setCredentials (
373+ GoogleCredentials .fromStream (new FileInputStream (credentialsFile )));
305374 } catch (IOException e ) {
306375 throw new RuntimeException (
307376 String .format (
@@ -310,6 +379,9 @@ protected LoggingOptions getLoggingOptions() {
310379 e );
311380 }
312381 }
382+ // opt-out metadata auto-population to control it in the appender code
383+ builder .setAutoPopulateMetadata (false );
384+ loggingOptions = builder .build ();
313385 }
314386 return loggingOptions ;
315387 }
@@ -328,7 +400,7 @@ private LogEntry logEntryFor(ILoggingEvent e) {
328400 }
329401 LogEntry .Builder builder =
330402 LogEntry .newBuilder (Payload .JsonPayload .of (jsonContent ))
331- .setTimestamp (e .getTimeStamp ())
403+ .setTimestamp (Instant . ofEpochMilli ( e .getTimeStamp () ))
332404 .setSeverity (severity );
333405 builder
334406 .addLabel (LEVEL_NAME_KEY , level .toString ())
0 commit comments