Skip to content

Conversation

@sustacek
Copy link

@sustacek sustacek commented Jun 18, 2025

Hi, I wanted to be able to use the JsonEncoder for Logback natively with SpringBoot, but for now I always have to create a logback.xml. SpringBoot 3.4 introduced support for structured formatting, without the need to write logback.xml / log4j.xml.

To support his, I created a simple wrapper. Then user will be able to use the encoder as a formatter for structured logging with just a regular application.properties / .yaml, like this:

# application.yaml
logging:
  structured:
    format:
      console: com.sap.hcp.cf.logback.boot.CloudFoundryStructuredLogFormatter
      #console: ecs
  level:
    root: INFO
    com.sap.cloud.sdk: INFO
    com.sap.cloud.security: DEBUG

Let me know what you think. In our application, I've created a similar wrapper, but since JsonEncoder does not expose the getJson(event) method, it's a little bit cumbersome -- I had to use reflection. I didn't want to use the public encode(event) because I'd need to decode it right back using UTF-8 into a String instance, every time.

Alternatively, if you could expose the JsonEncoder.getJson(event) method (or a new one, returning JSON as String), that'd help us a lot as well. I know this commit drags with itself a dependency on SpringBoot, which might not be ideal.

KarstenSchnitter and others added 28 commits February 28, 2024 16:05
The Dynatrace OTel metrics API requires DELTA temporality,
since CUMULATIVE is currently unsupported. This change will
allow configuration of the temporality via

`otel.exporter.dynatrace.metrics.temporality.preference`

This config now defaults to "delta" instead to the hard-coded
"cumulative" before. Usually, no configuration is required.

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
Co-authored-by: Anika Solanka <anika.solanka@sap.com>
Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
Dynatrace only accepts "delta" aggregation temporality for counters.
The "deltaPreferred" option still uses cumulative temporality for
up-and-down-counters. This lets Dynatrace drop the data altogether.
This commit will send all counters with delta aggregation temporality.

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
…ence

* Previously the present of a field name was checked after fetchin the header value and then dropped.
  This can be done once and during initialization.
* In the default flow it is not necessary. But since the DynamicLogLevelConfiguration is also exposed via the protected getConfiguration(), it is recreated on each invocation.
…AP#183)

* Directly extract correlation-id from traceparent without creating a string array and two not necessary substrings.

* Remove not(<>) predicate since it leads (very likely) to allocation. By using ?: it could be formulated without negation.
* traceparent parsing without String[] and not required substring creation.
Fix Aggregation Temporality in Dynatrace Exporter
AddHttpHeaderToLogContextFilter: Filter immediately for getField presence
Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
This change was tested to work well with the upgraded dependency.

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
Upgrade Java-JWT to version 4
Align community contribution files with <https://github.com/SAP/.github/>.

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
Bumps org.eclipse.jetty:jetty-server from 11.0.14 to 11.0.24.

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps org.eclipse.jetty:jetty-server from 9.4.51.v20230217 to 9.4.55.v20240627.

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
addresses SAP#193

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
[chore]:Reuse API update - Migration from dep5 file to TOML file.
Bumps org.eclipse.jetty:jetty-server from 9.4.55.v20240627 to 9.4.56.v20240826.

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-version: 9.4.56.v20240826
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
To avoid version conflicts, this dependency was removed.
The logic was rewritten to only use the JsonParser provided
by Jackson from the OTel Java Agent.

The resource provider is now built on the community version
of the CF resources and can even emit the attributes following
the official semantic conventions by configuration.

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
Remove dependency to class not included in shaded jar.

Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
…support-servlet/org.eclipse.jetty-jetty-server-9.4.56.v20240826

Bump org.eclipse.jetty:jetty-server from 9.4.55.v20240627 to 9.4.56.v20240826 in /cf-java-logging-support-servlet
Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
@cla-assistant
Copy link

cla-assistant bot commented Jun 18, 2025

CLA assistant check
All committers have signed the CLA.

@KarstenSchnitter
Copy link
Contributor

Hi,

thanks for raising the issue. I had a look at the participating classes and interfaces and must say, this raises some eyebrows. Let's start with ch.qos.logback.core.encoder.Encoder which is implemented by com.sap.hcp.cf.logback.encoder.JsonEncoder.

public interface Encoder<E> extends ContextAware, LifeCycle {
    byte[] headerBytes();

    byte[] encode(E var1);

    byte[] footerBytes();
}

Logback is expecting an Encoder to transform an event of type E (usually ILoggingEvent) into a byte array. Spring on the other hand defines org.springframework.boot.logging.structured.StructuredLogFormatter.

@FunctionalInterface
public interface StructuredLogFormatter<E> {

	String format(E event);

	default byte[] formatAsBytes(E event, Charset charset) {
		return format(event).getBytes(charset);
	}
}

Spring is expecting implementations of StructuredLogFormatter to emit String instances instead of byte arrays but provides a default implementation to transform the String into a byte array. This is a strange decision given what Logback provides. But comparing against Log4j2 it is very similar to the interfaces we find in that framework.

Now the interesting part happens, when we look where the StructuredLogFormatter is actually used: org.springframework.boot.logging.logback.StructuredLogEncoder:

public class StructuredLogEncoder extends EncoderBase<ILoggingEvent> {

	@Override
	public byte[] headerBytes() {
		return null;
	}

	@Override
	public byte[] encode(ILoggingEvent event) {
		return this.formatter.formatAsBytes(event, (this.charset != null) ? this.charset : StandardCharsets.UTF_8);
	}

	@Override
	public byte[] footerBytes() {
		return null;
	}
}

If you use Logback, the String based method is only called indirectly over the default method. That means in your wrapper you can overwrite the formatAsBytes method to directly call the JsonEncoder. For format you can transform the byte array into a String without performance penalty, since this code path will not be executed. You could throw an UnsupportedOperationException for testing.

For this library, I am open to change the visibility of JsonEncoder.getJson to public to ease the integration with Spring. Due to the viable work-around explained above, I do not think, that this warrants a release of the library on its own. I am also open to provide a StructuredLogFormatter in a separate module of cf-java-logging-support, but only in the new major release 4.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants