This is an automated email from the ASF dual-hosted git repository.

jiadongb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new e908c60de8 chore: make service log levels configurable via 
TEXERA_SERVICE_LOG_LEVEL (#4244)
e908c60de8 is described below

commit e908c60de85ec42fa3be4d8e82c9fcc96e970d0b
Author: Jiadong Bai <[email protected]>
AuthorDate: Fri Feb 27 20:35:16 2026 -0800

    chore: make service log levels configurable via TEXERA_SERVICE_LOG_LEVEL 
(#4244)
    
    ### What changes were proposed in this PR?
    
    This PR makes log levels configurable across all 7 Texera services via a
    single `TEXERA_SERVICE_LOG_LEVEL` environment variable, defaulting to
    `INFO`.
    
    Changes include:
    - Enable Dropwizard's `EnvironmentVariableSubstitutor` in each service's
    `initialize()` to support `${VAR:-default}` syntax in YAML configs
    - Replace all hardcoded `level: INFO` with `level:
    ${TEXERA_SERVICE_LOG_LEVEL:-INFO}` in Dropwizard YAML configs,
    logback.xml files, and Pekko cluster.conf
    - Disable Dropwizard's built-in `requestLog` console appender (which
    uses a separate logback-access pipeline not controllable by standard log
    levels) and replace it with a servlet filter that routes HTTP access
    logs through SLF4J at INFO level
    - Extract shared `RequestLoggingFilter` class in `common/auth` for the 4
    DW 4.x services; DW 1.3.x services use inline filters with a TODO to
    migrate after Dropwizard upgrade
    - Add `TEXERA_SERVICE_LOG_LEVEL=INFO` to `bin/single-node/.env`
    
    ### Any related issues, documentation, discussions?
    
    Closes #4243
    
    ### How was this PR tested?
    
    - Manually set `TEXERA_SERVICE_LOG_LEVEL=ERROR` and verified only
    error-level logs appear (no HTTP access logs, no INFO messages)
    - Manually set `TEXERA_SERVICE_LOG_LEVEL=INFO` (or left unset) and
    verified normal logging behavior including HTTP access logs
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Claude Opus 4.6)
---
 .../access-control-service-web-config.yaml         |  7 ++-
 .../src/main/resources/logback.xml                 |  2 +-
 .../texera/service/AccessControlService.scala      | 14 ++++-
 .../resources/computing-unit-master-config.yml     |  8 +--
 amber/src/main/resources/logback.xml               |  2 +-
 .../texera-compiling-service-web-config.yml        |  5 +-
 amber/src/main/resources/web-config.yml            |  5 +-
 .../apache/texera/web/ComputingUnitMaster.scala    | 35 ++++++++++++
 .../apache/texera/web/TexeraWebApplication.scala   | 36 +++++++++++-
 .../web/service/ExecutionResultService.scala       |  3 +-
 bin/single-node/.env                               |  3 +
 common/auth/build.sbt                              |  4 +-
 .../apache/texera/auth/RequestLoggingFilter.scala  | 64 ++++++++++++++++++++++
 common/config/src/main/resources/cluster.conf      |  1 +
 .../computing-unit-managing-service-config.yaml    |  7 ++-
 .../service/ComputingUnitManagingService.scala     | 14 ++++-
 .../main/resources/config-service-web-config.yaml  |  7 ++-
 .../org/apache/texera/service/ConfigService.scala  | 13 ++++-
 .../main/resources/file-service-web-config.yaml    |  7 ++-
 .../org/apache/texera/service/FileService.scala    | 14 ++++-
 .../workflow-compiling-service-config.yaml         |  7 ++-
 .../texera/service/WorkflowCompilingService.scala  | 32 +++++++++++
 22 files changed, 256 insertions(+), 34 deletions(-)

diff --git 
a/access-control-service/src/main/resources/access-control-service-web-config.yaml
 
b/access-control-service/src/main/resources/access-control-service-web-config.yaml
index e8d17cec28..8c7895e985 100644
--- 
a/access-control-service/src/main/resources/access-control-service-web-config.yaml
+++ 
b/access-control-service/src/main/resources/access-control-service-web-config.yaml
@@ -20,12 +20,15 @@ server:
     - type: http
       port: 9096
   adminConnectors: []
+  requestLog:
+    type: classic
+    appenders: []
 
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
-      threshold: INFO
+      threshold: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
     - type: file
       currentLogFilename: logs/access-control-service.log
       archive: true
diff --git a/access-control-service/src/main/resources/logback.xml 
b/access-control-service/src/main/resources/logback.xml
index 6a5e4871b4..99794f1968 100644
--- a/access-control-service/src/main/resources/logback.xml
+++ b/access-control-service/src/main/resources/logback.xml
@@ -45,7 +45,7 @@
         <appender-ref ref="FILE"/>
     </appender>
 
-    <root level="INFO">
+    <root level="${TEXERA_SERVICE_LOG_LEVEL:-INFO}">
         <appender-ref ref="ASYNC"/>
         <appender-ref ref="STDOUT"/>
     </root>
diff --git 
a/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
 
b/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
index 7fac58328d..0ab9f0fbfe 100644
--- 
a/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
+++ 
b/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
@@ -20,10 +20,11 @@ package org.apache.texera.service
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.typesafe.scalalogging.LazyLogging
 import io.dropwizard.auth.AuthDynamicFeature
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.auth.{JwtAuthFilter, SessionUser}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.resource.{
   AccessControlResource,
@@ -32,11 +33,17 @@ import org.apache.texera.service.resource.{
   LiteLLMProxyResource
 }
 import org.eclipse.jetty.server.session.SessionHandler
-
 import java.nio.file.Path
 
 class AccessControlService extends 
Application[AccessControlServiceConfiguration] with LazyLogging {
   override def initialize(bootstrap: 
Bootstrap[AccessControlServiceConfiguration]): Unit = {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // Register Scala module to Dropwizard default object mapper
     bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
 
@@ -69,6 +76,9 @@ class AccessControlService extends 
Application[AccessControlServiceConfiguration
     environment.jersey.register(
       new 
io.dropwizard.auth.AuthValueFactoryProvider.Binder(classOf[SessionUser])
     )
+
+    // Route request logs through SLF4J, controlled by TEXERA_SERVICE_LOG_LEVEL
+    RequestLoggingFilter.register(environment.getApplicationContext)
   }
 }
 object AccessControlService {
diff --git a/amber/src/main/resources/computing-unit-master-config.yml 
b/amber/src/main/resources/computing-unit-master-config.yml
index 88253f7812..0dba594b8a 100644
--- a/amber/src/main/resources/computing-unit-master-config.yml
+++ b/amber/src/main/resources/computing-unit-master-config.yml
@@ -28,14 +28,12 @@ server:
   requestLog:
     type: classic
     timeZone: UTC
-    appenders:
-      - type: console
-        threshold: ERROR  # Only log errors in console for simplicity
+    appenders: []
 
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   loggers:
-    "io.dropwizard": INFO
+    "io.dropwizard": ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
       logFormat: "[%date{ISO8601}] [%level] [%logger] [%thread] - %msg %n"
diff --git a/amber/src/main/resources/logback.xml 
b/amber/src/main/resources/logback.xml
index aff1ee485e..43afb5d44c 100644
--- a/amber/src/main/resources/logback.xml
+++ b/amber/src/main/resources/logback.xml
@@ -46,7 +46,7 @@
         <appender-ref ref="FILE"/>
     </appender>
 
-    <root level="INFO">
+    <root level="${TEXERA_SERVICE_LOG_LEVEL:-INFO}">
         <appender-ref ref="ASYNC"/>
         <appender-ref ref="STDOUT"/>
     </root>
diff --git a/amber/src/main/resources/texera-compiling-service-web-config.yml 
b/amber/src/main/resources/texera-compiling-service-web-config.yml
index 94a00fc453..ea2c1b9c1e 100644
--- a/amber/src/main/resources/texera-compiling-service-web-config.yml
+++ b/amber/src/main/resources/texera-compiling-service-web-config.yml
@@ -29,7 +29,6 @@ server:
     type: classic
     timeZone: UTC
     appenders:
-      - type: console
       - type: file
         currentLogFilename: logs/access.log
         threshold: ALL
@@ -41,9 +40,9 @@ server:
         bufferSize: 8KiB
         immediateFlush: true
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   loggers:
-    "io.dropwizard": INFO
+    "io.dropwizard": ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
       logFormat: "[%date{ISO8601}] [%level] [%logger] [%thread] - %msg %n"
diff --git a/amber/src/main/resources/web-config.yml 
b/amber/src/main/resources/web-config.yml
index c777cecd85..9fde1d078e 100644
--- a/amber/src/main/resources/web-config.yml
+++ b/amber/src/main/resources/web-config.yml
@@ -29,7 +29,6 @@ server:
     type: classic
     timeZone: UTC
     appenders:
-      - type: console
       - type: file
         currentLogFilename: logs/access.log
         threshold: ALL
@@ -41,9 +40,9 @@ server:
         bufferSize: 8KiB
         immediateFlush: true
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   loggers:
-    "io.dropwizard": INFO
+    "io.dropwizard": ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
       logFormat: "[%date{ISO8601}] [%level] [%logger] [%thread] - %msg %n"
diff --git 
a/amber/src/main/scala/org/apache/texera/web/ComputingUnitMaster.scala 
b/amber/src/main/scala/org/apache/texera/web/ComputingUnitMaster.scala
index 225f5c152a..4b07113b1c 100644
--- a/amber/src/main/scala/org/apache/texera/web/ComputingUnitMaster.scala
+++ b/amber/src/main/scala/org/apache/texera/web/ComputingUnitMaster.scala
@@ -22,6 +22,7 @@ package org.apache.texera.web
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.typesafe.scalalogging.LazyLogging
 import io.dropwizard.Configuration
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.setup.{Bootstrap, Environment}
 import io.dropwizard.websockets.WebsocketBundle
 import org.apache.texera.amber.config.{ApplicationConfig, StorageConfig}
@@ -49,6 +50,7 @@ import 
org.apache.texera.web.resource.dashboard.user.workflow.WorkflowExecutions
 import org.apache.texera.web.resource.{WebsocketPayloadSizeTuner, 
WorkflowWebsocketResource}
 import org.apache.texera.web.service.ExecutionsMetadataPersistService
 import org.eclipse.jetty.server.session.SessionHandler
+import org.eclipse.jetty.servlet.FilterHolder
 import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter
 
 import java.net.URI
@@ -112,6 +114,13 @@ object ComputingUnitMaster {
 class ComputingUnitMaster extends io.dropwizard.Application[Configuration] 
with LazyLogging {
 
   override def initialize(bootstrap: Bootstrap[Configuration]): Unit = {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // add websocket bundle
     bootstrap.addBundle(new 
WebsocketBundle(classOf[WorkflowWebsocketResource]))
     // register scala module to dropwizard default object mapper
@@ -180,6 +189,32 @@ class ComputingUnitMaster extends 
io.dropwizard.Application[Configuration] with
     }
 
     environment.jersey.register(classOf[WorkflowExecutionsResource])
+
+    // Route request logs through SLF4J, controlled by 
TEXERA_SERVICE_LOG_LEVEL.
+    // TODO: replace with RequestLoggingFilter.register() from common/auth 
once Dropwizard is upgraded to 4.x
+    val requestLogger = 
org.slf4j.LoggerFactory.getLogger("org.eclipse.jetty.server.RequestLog")
+    environment.getApplicationContext.addFilter(
+      new FilterHolder(new javax.servlet.Filter {
+        override def init(filterConfig: javax.servlet.FilterConfig): Unit = {}
+        override def doFilter(
+            request: javax.servlet.ServletRequest,
+            response: javax.servlet.ServletResponse,
+            chain: javax.servlet.FilterChain
+        ): Unit = {
+          chain.doFilter(request, response)
+          if (requestLogger.isInfoEnabled) {
+            val req = 
request.asInstanceOf[javax.servlet.http.HttpServletRequest]
+            val resp = 
response.asInstanceOf[javax.servlet.http.HttpServletResponse]
+            requestLogger.info(
+              s"""${req.getRemoteAddr} - "${req.getMethod} 
${req.getRequestURI} ${req.getProtocol}" ${resp.getStatus}"""
+            )
+          }
+        }
+        override def destroy(): Unit = {}
+      }),
+      "/*",
+      java.util.EnumSet.allOf(classOf[javax.servlet.DispatcherType])
+    )
   }
 
   /**
diff --git 
a/amber/src/main/scala/org/apache/texera/web/TexeraWebApplication.scala 
b/amber/src/main/scala/org/apache/texera/web/TexeraWebApplication.scala
index 4264a9ca18..98b7c68c97 100644
--- a/amber/src/main/scala/org/apache/texera/web/TexeraWebApplication.scala
+++ b/amber/src/main/scala/org/apache/texera/web/TexeraWebApplication.scala
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.github.dirkraft.dropwizard.fileassets.FileAssetsBundle
 import com.typesafe.scalalogging.LazyLogging
 import io.dropwizard.auth.AuthValueFactoryProvider
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.setup.{Bootstrap, Environment}
 import io.dropwizard.websockets.WebsocketBundle
 import org.apache.texera.amber.config.StorageConfig
@@ -52,7 +53,7 @@ import 
org.apache.texera.web.resource.dashboard.user.workflow.{
   WorkflowVersionResource
 }
 import org.eclipse.jetty.server.session.SessionHandler
-import org.eclipse.jetty.servlet.ErrorPageErrorHandler
+import org.eclipse.jetty.servlet.{ErrorPageErrorHandler, FilterHolder}
 import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter
 import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature
 
@@ -84,6 +85,13 @@ class TexeraWebApplication
     with LazyLogging {
 
   override def initialize(bootstrap: Bootstrap[TexeraWebConfiguration]): Unit 
= {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // serve static frontend GUI files
     bootstrap.addBundle(new FileAssetsBundle("../../frontend/dist", "/", 
"index.html"))
     // add websocket bundle
@@ -154,5 +162,31 @@ class TexeraWebApplication
     environment.jersey.register(classOf[AIAssistantResource])
 
     AuthResource.createAdminUser()
+
+    // Route request logs through SLF4J, controlled by 
TEXERA_SERVICE_LOG_LEVEL.
+    // TODO: replace with RequestLoggingFilter.register() from common/auth 
once Dropwizard is upgraded to 4.x
+    val requestLogger = 
org.slf4j.LoggerFactory.getLogger("org.eclipse.jetty.server.RequestLog")
+    environment.getApplicationContext.addFilter(
+      new FilterHolder(new javax.servlet.Filter {
+        override def init(filterConfig: javax.servlet.FilterConfig): Unit = {}
+        override def doFilter(
+            request: javax.servlet.ServletRequest,
+            response: javax.servlet.ServletResponse,
+            chain: javax.servlet.FilterChain
+        ): Unit = {
+          chain.doFilter(request, response)
+          if (requestLogger.isInfoEnabled) {
+            val req = 
request.asInstanceOf[javax.servlet.http.HttpServletRequest]
+            val resp = 
response.asInstanceOf[javax.servlet.http.HttpServletResponse]
+            requestLogger.info(
+              s"""${req.getRemoteAddr} - "${req.getMethod} 
${req.getRequestURI} ${req.getProtocol}" ${resp.getStatus}"""
+            )
+          }
+        }
+        override def destroy(): Unit = {}
+      }),
+      "/*",
+      java.util.EnumSet.allOf(classOf[javax.servlet.DispatcherType])
+    )
   }
 }
diff --git 
a/amber/src/main/scala/org/apache/texera/web/service/ExecutionResultService.scala
 
b/amber/src/main/scala/org/apache/texera/web/service/ExecutionResultService.scala
index 285c836b60..3f0362f824 100644
--- 
a/amber/src/main/scala/org/apache/texera/web/service/ExecutionResultService.scala
+++ 
b/amber/src/main/scala/org/apache/texera/web/service/ExecutionResultService.scala
@@ -23,8 +23,7 @@ import org.apache.pekko.actor.Cancellable
 import com.fasterxml.jackson.annotation.{JsonTypeInfo, JsonTypeName}
 import com.fasterxml.jackson.databind.node.ObjectNode
 import com.typesafe.scalalogging.LazyLogging
-import org.apache.texera.amber.config.{ApplicationConfig, StorageConfig}
-import org.apache.texera.amber.core.storage.DocumentFactory.ICEBERG
+import org.apache.texera.amber.config.ApplicationConfig
 import org.apache.texera.amber.core.storage.model.VirtualDocument
 import org.apache.texera.amber.core.storage.result._
 import org.apache.texera.amber.core.storage.{DocumentFactory, VFSURIFactory}
diff --git a/bin/single-node/.env b/bin/single-node/.env
index 0b9c4916af..4da884bab0 100644
--- a/bin/single-node/.env
+++ b/bin/single-node/.env
@@ -17,6 +17,9 @@
 
 TEXERA_HOST=http://localhost
 
+# Log level for all Texera services (valid values: ERROR, WARN, INFO, DEBUG)
+TEXERA_SERVICE_LOG_LEVEL=INFO
+
 # Container image configuration
 # Override these to use a different registry or image version
 IMAGE_REGISTRY=ghcr.io/apache
diff --git a/common/auth/build.sbt b/common/auth/build.sbt
index a89812d80c..3d2b3685c1 100644
--- a/common/auth/build.sbt
+++ b/common/auth/build.sbt
@@ -59,5 +59,7 @@ libraryDependencies ++= Seq(
   "com.typesafe" % "config" % "1.4.3",                                  // 
config reader
   "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5",            // for 
LazyLogging
   "org.bitbucket.b_c" % "jose4j" % "0.9.6",                             // for 
jwt parser
-  "jakarta.ws.rs" % "jakarta.ws.rs-api" % "3.0.0"                       // for 
JwtAuthFilter
+  "jakarta.ws.rs" % "jakarta.ws.rs-api" % "3.0.0",                      // for 
JwtAuthFilter
+  "jakarta.servlet" % "jakarta.servlet-api" % "5.0.0" % "provided",    // for 
RequestLoggingFilter
+  "org.eclipse.jetty" % "jetty-servlet" % "11.0.24" % "provided"       // for 
FilterHolder
 )
\ No newline at end of file
diff --git 
a/common/auth/src/main/scala/org/apache/texera/auth/RequestLoggingFilter.scala 
b/common/auth/src/main/scala/org/apache/texera/auth/RequestLoggingFilter.scala
new file mode 100644
index 0000000000..9a1587d0f1
--- /dev/null
+++ 
b/common/auth/src/main/scala/org/apache/texera/auth/RequestLoggingFilter.scala
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.texera.auth
+
+import jakarta.servlet._
+import jakarta.servlet.http.{HttpServletRequest, HttpServletResponse}
+import org.slf4j.LoggerFactory
+
+/**
+  * Servlet filter that logs HTTP requests through SLF4J at INFO level.
+  * This replaces Dropwizard's built-in request log (which uses a separate
+  * access log pipeline not controllable by log level) so that request logs
+  * are fully controlled by the TEXERA_SERVICE_LOG_LEVEL environment variable.
+  */
+class RequestLoggingFilter extends Filter {
+  private val logger = 
LoggerFactory.getLogger("org.eclipse.jetty.server.RequestLog")
+
+  override def doFilter(
+      request: ServletRequest,
+      response: ServletResponse,
+      chain: FilterChain
+  ): Unit = {
+    chain.doFilter(request, response)
+    if (logger.isInfoEnabled) {
+      val req = request.asInstanceOf[HttpServletRequest]
+      val resp = response.asInstanceOf[HttpServletResponse]
+      logger.info(
+        s"""${req.getRemoteAddr} - "${req.getMethod} ${req.getRequestURI} 
${req.getProtocol}" ${resp.getStatus}"""
+      )
+    }
+  }
+}
+
+object RequestLoggingFilter {
+
+  /**
+    * Registers the request logging filter on the given servlet context.
+    * Usage: RequestLoggingFilter.register(environment.getApplicationContext)
+    */
+  def register(context: org.eclipse.jetty.servlet.ServletContextHandler): Unit 
= {
+    context.addFilter(
+      new org.eclipse.jetty.servlet.FilterHolder(new RequestLoggingFilter),
+      "/*",
+      java.util.EnumSet.allOf(classOf[DispatcherType])
+    )
+  }
+}
diff --git a/common/config/src/main/resources/cluster.conf 
b/common/config/src/main/resources/cluster.conf
index adf42fccde..524cd0216d 100644
--- a/common/config/src/main/resources/cluster.conf
+++ b/common/config/src/main/resources/cluster.conf
@@ -24,6 +24,7 @@ pekko {
     # as they have been started; before that, see "stdout-loglevel"
     # Options: OFF, ERROR, WARNING, INFO, DEBUG
     loglevel = "INFO"
+    loglevel = ${?TEXERA_SERVICE_LOG_LEVEL}
 
     # Log level for the very basic logger activated during ActorSystem startup.
     # This logger prints the log messages to stdout (System.out).
diff --git 
a/computing-unit-managing-service/src/main/resources/computing-unit-managing-service-config.yaml
 
b/computing-unit-managing-service/src/main/resources/computing-unit-managing-service-config.yaml
index ed1cb62e6c..523b419798 100644
--- 
a/computing-unit-managing-service/src/main/resources/computing-unit-managing-service-config.yaml
+++ 
b/computing-unit-managing-service/src/main/resources/computing-unit-managing-service-config.yaml
@@ -23,8 +23,11 @@ server:
   adminConnectors:
     - type: http
       port: 8082
+  requestLog:
+    type: classic
+    appenders: []
 
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   loggers:
-    "com.example": DEBUG
\ No newline at end of file
+    "com.example": ${TEXERA_SERVICE_LOG_LEVEL:-DEBUG}
\ No newline at end of file
diff --git 
a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
 
b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
index 1c0cf8768d..a15ced30a2 100644
--- 
a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
+++ 
b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
@@ -21,17 +21,17 @@ package org.apache.texera.service
 
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import io.dropwizard.auth.AuthDynamicFeature
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.auth.{JwtAuthFilter, SessionUser}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.resource.{
   ComputingUnitAccessResource,
   ComputingUnitManagingResource,
   HealthCheckResource
 }
-
 import java.nio.file.Path
 
 class ComputingUnitManagingService extends 
Application[ComputingUnitManagingServiceConfiguration] {
@@ -39,6 +39,13 @@ class ComputingUnitManagingService extends 
Application[ComputingUnitManagingServ
   override def initialize(
       bootstrap: Bootstrap[ComputingUnitManagingServiceConfiguration]
   ): Unit = {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // register scala module to dropwizard default object mapper
     bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
   }
@@ -65,6 +72,9 @@ class ComputingUnitManagingService extends 
Application[ComputingUnitManagingServ
 
     environment.jersey().register(new ComputingUnitManagingResource)
     environment.jersey().register(new ComputingUnitAccessResource)
+
+    // Route request logs through SLF4J, controlled by TEXERA_SERVICE_LOG_LEVEL
+    RequestLoggingFilter.register(environment.getApplicationContext)
   }
 }
 
diff --git a/config-service/src/main/resources/config-service-web-config.yaml 
b/config-service/src/main/resources/config-service-web-config.yaml
index 45a1ff8dd1..4aa67af82e 100644
--- a/config-service/src/main/resources/config-service-web-config.yaml
+++ b/config-service/src/main/resources/config-service-web-config.yaml
@@ -20,12 +20,15 @@ server:
     - type: http
       port: 9094
   adminConnectors: []
+  requestLog:
+    type: classic
+    appenders: []
 
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
-      threshold: INFO
+      threshold: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
     - type: file
       currentLogFilename: logs/config-service.log
       archive: true
diff --git 
a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala 
b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala
index c226eaf9c4..c787016c27 100644
--- 
a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala
+++ 
b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala
@@ -22,10 +22,11 @@ package org.apache.texera.service
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.typesafe.scalalogging.LazyLogging
 import io.dropwizard.auth.AuthDynamicFeature
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.auth.{JwtAuthFilter, SessionUser}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.config.DefaultsConfig
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.resource.{ConfigResource, HealthCheckResource}
@@ -36,6 +37,13 @@ import java.nio.file.Path
 
 class ConfigService extends Application[ConfigServiceConfiguration] with 
LazyLogging {
   override def initialize(bootstrap: Bootstrap[ConfigServiceConfiguration]): 
Unit = {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // Register Scala module to Dropwizard default object mapper
     bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
 
@@ -94,6 +102,9 @@ class ConfigService extends 
Application[ConfigServiceConfiguration] with LazyLog
         logger.error("Failed to preload default settings", ex)
         throw ex
     }
+
+    // Route request logs through SLF4J, controlled by TEXERA_SERVICE_LOG_LEVEL
+    RequestLoggingFilter.register(environment.getApplicationContext)
   }
 }
 
diff --git a/file-service/src/main/resources/file-service-web-config.yaml 
b/file-service/src/main/resources/file-service-web-config.yaml
index e9c0e33c05..41f8d1b174 100644
--- a/file-service/src/main/resources/file-service-web-config.yaml
+++ b/file-service/src/main/resources/file-service-web-config.yaml
@@ -20,11 +20,14 @@ server:
     - type: http
       port: 9092
   adminConnectors: []
+  requestLog:
+    type: classic
+    appenders: []
 
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   loggers:
-    "io.dropwizard": INFO
+    "io.dropwizard": ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
     - type: file
diff --git 
a/file-service/src/main/scala/org/apache/texera/service/FileService.scala 
b/file-service/src/main/scala/org/apache/texera/service/FileService.scala
index a92eaca511..20bb242bc1 100644
--- a/file-service/src/main/scala/org/apache/texera/service/FileService.scala
+++ b/file-service/src/main/scala/org/apache/texera/service/FileService.scala
@@ -23,11 +23,12 @@ import com.fasterxml.jackson.databind.module.SimpleModule
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
 import com.typesafe.scalalogging.LazyLogging
 import io.dropwizard.auth.AuthDynamicFeature
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
 import org.apache.texera.amber.core.storage.util.LakeFSStorageClient
-import org.apache.texera.auth.{JwtAuthFilter, SessionUser}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.`type`.DatasetFileNode
 import org.apache.texera.service.`type`.serde.DatasetFileNodeSerializer
@@ -38,11 +39,17 @@ import org.apache.texera.service.resource.{
 }
 import org.apache.texera.service.util.S3StorageClient
 import org.eclipse.jetty.server.session.SessionHandler
-
 import java.nio.file.Path
 
 class FileService extends Application[FileServiceConfiguration] with 
LazyLogging {
   override def initialize(bootstrap: Bootstrap[FileServiceConfiguration]): 
Unit = {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // Register Scala module to Dropwizard default object mapper
     bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
 
@@ -81,6 +88,9 @@ class FileService extends 
Application[FileServiceConfiguration] with LazyLogging
 
     environment.jersey.register(classOf[DatasetResource])
     environment.jersey.register(classOf[DatasetAccessResource])
+
+    // Route request logs through SLF4J, controlled by TEXERA_SERVICE_LOG_LEVEL
+    RequestLoggingFilter.register(environment.getApplicationContext)
   }
 }
 
diff --git 
a/workflow-compiling-service/src/main/resources/workflow-compiling-service-config.yaml
 
b/workflow-compiling-service/src/main/resources/workflow-compiling-service-config.yaml
index 6ad15eaeb2..5b9016af1b 100644
--- 
a/workflow-compiling-service/src/main/resources/workflow-compiling-service-config.yaml
+++ 
b/workflow-compiling-service/src/main/resources/workflow-compiling-service-config.yaml
@@ -20,11 +20,14 @@ server:
     - type: http
       port: 9090
   adminConnectors: []
+  requestLog:
+    type: classic
+    appenders: []
 
 logging:
-  level: INFO
+  level: ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   loggers:
-    "io.dropwizard": INFO
+    "io.dropwizard": ${TEXERA_SERVICE_LOG_LEVEL:-INFO}
   appenders:
     - type: console
     - type: file
diff --git 
a/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala
 
b/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala
index 27aad902fe..40fb3a2dd8 100644
--- 
a/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala
+++ 
b/workflow-compiling-service/src/main/scala/org/apache/texera/service/WorkflowCompilingService.scala
@@ -20,17 +20,26 @@
 package org.apache.texera.service
 
 import com.fasterxml.jackson.module.scala.DefaultScalaModule
+import io.dropwizard.configuration.{EnvironmentVariableSubstitutor, 
SubstitutingSourceProvider}
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
 import org.apache.texera.amber.util.ObjectMapperUtils
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.resource.{HealthCheckResource, 
WorkflowCompilationResource}
+import org.eclipse.jetty.servlet.FilterHolder
 
 import java.nio.file.Path
 
 class WorkflowCompilingService extends 
Application[WorkflowCompilingServiceConfiguration] {
   override def initialize(bootstrap: 
Bootstrap[WorkflowCompilingServiceConfiguration]): Unit = {
+    // enable environment variable substitution in YAML config
+    bootstrap.setConfigurationSourceProvider(
+      new SubstitutingSourceProvider(
+        bootstrap.getConfigurationSourceProvider,
+        new EnvironmentVariableSubstitutor(false)
+      )
+    )
     // register scala module to dropwizard default object mapper
     bootstrap.getObjectMapper.registerModule(DefaultScalaModule)
   }
@@ -54,6 +63,29 @@ class WorkflowCompilingService extends 
Application[WorkflowCompilingServiceConfi
 
     // register the compilation endpoint
     environment.jersey.register(classOf[WorkflowCompilationResource])
+
+    // Route request logs through SLF4J, controlled by TEXERA_SERVICE_LOG_LEVEL
+    val requestLogger = 
org.slf4j.LoggerFactory.getLogger("org.eclipse.jetty.server.RequestLog")
+    environment.getApplicationContext.addFilter(
+      new FilterHolder(new jakarta.servlet.Filter {
+        override def doFilter(
+            request: jakarta.servlet.ServletRequest,
+            response: jakarta.servlet.ServletResponse,
+            chain: jakarta.servlet.FilterChain
+        ): Unit = {
+          chain.doFilter(request, response)
+          if (requestLogger.isInfoEnabled) {
+            val req = 
request.asInstanceOf[jakarta.servlet.http.HttpServletRequest]
+            val resp = 
response.asInstanceOf[jakarta.servlet.http.HttpServletResponse]
+            requestLogger.info(
+              s"""${req.getRemoteAddr} - "${req.getMethod} 
${req.getRequestURI} ${req.getProtocol}" ${resp.getStatus}"""
+            )
+          }
+        }
+      }),
+      "/*",
+      java.util.EnumSet.allOf(classOf[jakarta.servlet.DispatcherType])
+    )
   }
 }
 


Reply via email to