SAP / cf-java-logging-support

The Java Logging Support for Cloud Foundry supports the creation of structured log messages and the collection of request metrics
Apache License 2.0
76 stars 46 forks source link

Add Custom Tenant Id in Request Logging Filter #172

Open amrit9326 opened 6 months ago

amrit9326 commented 6 months ago

Hi Team,

We're trying to add custom tenantID in the request logging filter but couldn't find anyway of doing it , it's taking the tenantID from the subdomain by default which we don't want.

We tried adding the tenantID by extending the different filter classes and overriding it's method where we're modifying the request header by adding the tenantID but it's being overrided with the subdomain.

Please check the code below

@Configuration
public class LogConfig {

  /**
   * Configures and returns a Spring Boot FilterRegistration bean. The FilterRegistration bean holds
   * a SAP CF RequestLoggingFilter {@link RequestLoggingFilter}. Required for automatic generation
   * and propagation of correlation ID.
   *
   * @return {@link FilterRegistrationBean}
   */

  @Bean
  public FilterRegistrationBean<Filter> filterRegistrationBean() {

    FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();

    RequestLoggingFilter requestLoggingFilter = new RequestLoggingFilter();
    registrationBean.setFilter(requestLoggingFilter);

    return registrationBean;
  }
}

@Component
public class TenantIDFilter extends AbstractLoggingFilter {

  @Override
  public void doFilter(
      ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    if (request instanceof HttpServletRequest req
        && response instanceof HttpServletResponse resp) {
      AddCustomHeaderWrapper requestWrapper = new AddCustomHeaderWrapper(req);
      requestWrapper.addHeader(Fields.TENANT_ID, SpringSecurityContext.getToken().getZoneId());
      this.doFilterRequest(requestWrapper, resp,
          chain);
    } else {
      chain.doFilter(request, response);
    }

  }
}
public class AddCustomHeaderWrapper extends HttpServletRequestWrapper {

  /**
   * construct a wrapper for this request
   *
   * @param request
   */

  private Map<String, String> headerMap = new HashMap<String, String>();

  public AddCustomHeaderWrapper(HttpServletRequest request) {
    super(request);
  }

  /**
   * add a header with given name and value
   *
   * @param name
   * @param value
   */
  public void addHeader(String name, String value) {
    headerMap.put(name, value);
  }

  @Override
  public String getHeader(String name) {
    String headerValue = super.getHeader(name);
    if (headerMap.containsKey(name)) {
      headerValue = headerMap.get(name);
    }
    return headerValue;
  }

  /**
   * get the Header names
   */
  @Override
  public Enumeration<String> getHeaderNames() {
    List<String> names = Collections.list(super.getHeaderNames());
    for (String name : headerMap.keySet()) {
      names.add(name);
    }
    return Collections.enumeration(names);
  }

  @Override
  public Enumeration<String> getHeaders(String name) {
    List<String> values = Collections.list(super.getHeaders(name));
    if (headerMap.containsKey(name)) {
      values.add(headerMap.get(name));
    }
    return Collections.enumeration(values);
  }

}

Please check the image below where tenantID is dvh and we're trying to replace it with some guid. image

KarstenSchnitter commented 6 months ago

HI @amrit9326,

thanks for creating the issue. I need a little while to investigate the issue. Here are some background informations to the way the tenant id is added, that might help until then:

The tenant-id is taken from the HTTP header tenantid by the AddHttpHeadersToLogContextFilter.java Have a look at this constructor on how to take the header out of the list of propagated HTTP headers. There is also a section in the documentation on (custom header propagation)[https://github.com/SAP/cf-java-logging-support/wiki/Instrumenting-Servlets#custom-http-header-propagation]. You might also want to have a look at the ContextFieldSupplier interface documented here.

Best Regards, Karsten

amrit9326 commented 6 months ago

Hi @KarstenSchnitter ,

Right now the tenantID is being mapped with the subdomain and I want to map it with the SpringSecurityContext.getToken().getZoneId() . Could you please let us know from which part of code implementation this can be done? Also I tried debugging where subdomain is being splitted and mapped with tenantID but didn't find anything so far.

KarstenSchnitter commented 6 months ago

Besides reading the tenantId from the HTTP header, there is no instrumentation for tenant ids in this library. That means, there is some other library, that is setting the tenant id on your behalf. I suggest to have a look at SAP Cloud ALM, if you use that. There is an old issue #122, that shows an implementation of a servlet filter, that adds a field to all log messages from the Spring Security context. Note, that it is not adding the tenant-id back as an HTTP header, but writes it directly to the MDC (wrapped by the LogContext). An alternative approach is to implement a ContextFieldSupplier like this:

package my.package;

public class TenantIdSupplier implements ContextFieldSupplier {
    @Override
    public Map<String, Object> get() {
        return new HashMap<>() {{
            put(Fields.TENANT_ID, SpringSecurityContext.getToken().getZoneId());
        }};
    }
}

You than need to add this class to your logback configuration as indicated here, e.g. a snippet for logback:

<appender name="STDOUT-JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="com.sap.hcp.cf.logback.encoder.JsonEncoder">
        <contextFieldSupplier>my.package.TenantIdSupplier</contextFieldSupplier>
        <!-- all other configuraton -->
    </encoder>
</appender>

The difference between both approaches is, when and how often the tenant id is extracted from the SecurityContext:

KarstenSchnitter commented 6 months ago

@amrit9326: How are you progressing? Is this issue still relevant?