Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic reporting through toString #220

Merged
merged 6 commits into from
Jun 1, 2023
Merged

Conversation

sherriesyt
Copy link
Contributor

This is super not ready, just opening an early draft for visibility. Meant for #188.

I think I realized after this first commit that since Otel4s is a trait, the implementation of toString shouldn't lie here (instead, it should just specify that a String is returned) -- is this correct? (And we leave the implementation to all the things with type Otel4s[F[_]] / all the things that extend Otel4s[F[_]]?)

@armanbilge armanbilge linked an issue May 18, 2023 that may be closed by this pull request
@iRevive
Copy link
Contributor

iRevive commented May 18, 2023

And we leave the implementation to all the things

You are correct. Currently, we have only one 'real' implementation here https://github.com/typelevel/otel4s/blob/main/java/all/src/main/scala/org/typelevel/otel4s/java/OtelJava.scala#L67.

We can override the toString right in this anonymous class.

The OpenTelemetry classes in Java, like io.opentelemetry.sdk.OpenTelemetrySdk, provide sufficient information in their toString implementation. We have two options: either delegate to jOtel.toString() or incorporate it into our own custom message.

For now, it seems like delegating should be enough.

@sherriesyt
Copy link
Contributor Author

So, I sat in a call with @armanbilge and wrote one test case to observe the output of JOpenTelemetry's noop implementation's toString method, and it's...not pretty.

8:   test("toString implementation") {
9:     assertEquals(JOpenTelemetry.noop().toString(), "noop?")
10:  }
values are not the same
=> Obtained
io.opentelemetry.api.DefaultOpenTelemetry@4299b15b
=> Diff (- obtained, + expected)
-io.opentelemetry.api.DefaultOpenTelemetry@4299b15b
+noop?

Wanted to get some opinions on how to proceed.

@@ -68,6 +68,8 @@ object OtelJava {
def propagators: ContextPropagators[F] = contextPropagators
def meterProvider: MeterProvider[F] = metrics.meterProvider
def tracerProvider: TracerProvider[F] = traces.tracerProvider

override def toString: String = jOtel.toString()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the Java OpenTelemetry#toString is not guaranteed to be nice in general, but we can still use propgators.toString, meterProvider.toString, tracerProvider.toString etc. to put together our own toString?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uninformative .toString you're getting is the default from java.lang.Object.

In general, the Java Otel API is built on interfaces. I wrote 99% of my Java before interfaces could have default implementations, and I don't know whether an interface can override java.lang.Object#toString. Legal or not, I can confirm some, including OpenTelemetry, don't. "Not guaranteed to be nice" is correct.

Some implementations have a .toString, like DefaultContextPropagators. Some do not, like DefaultTracerProvider. So you'd make some progress going down that road, but won't be rid of the hexadecimal hashCodes, and ... there can be multiple implementations of these interfaces. See "no guarantees."

The golden rule of software quality tells us to upstream better .toStrings to Java, but that's not what @sherriesyt volunteered for. If that's interesting anyway, I can help. If it's not... I've been writing Java for a quarter century, and another hour probably wouldn't kill me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind dabbling in Java (though I'm rusty) -- can you point me to where to start? Is this somewhere in opentelemetry-java?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The noop value in your test is implemented here, and a .toString in that class would make it nicer.

The one I would expect to see in production is here, and already has a thorough toString. So while the easy one to test is in rough shape, the good news is the common production one should already work pretty well!

If you decide to chase this upstream, the build tool is Gradle. I've used gradle publishToMavenLocal before. You can see the version number it generates in the output (I don't have it handy to be exact), and you can put that version into build.sbt to see how your work interacts with Otel4s.

@rossabaker
Copy link
Member

There are a lot of checks for formatting and licenses and what not. There's a prePR command added to SBT that runs them all. I'm not in the habit of running it myself, but it's a way to find them locally instead of one-by-one in GitHub Actions.

scalafmtSbt should fix the most recent build failure. You'd think scalafmtAll formats all, but it doesn't format SBT. 🤷

@sherriesyt
Copy link
Contributor Author

sherriesyt commented May 23, 2023

Ah, you got around to commenting first, I pushed and in the midst of writing up a comment here my stomach went "I ache now."1

I did the lazy2 thing and tried out the other toString method you linked. It looks substantially more informative, though maybe a little visually messy?

One small question out of this is that I don't have a clean way of asserting that the output equals some expected string, because (e.g.) NoopLogRecordProcessor@{insert-hex-here} (that is part of the toString output) will always have some different hex code. Any advice on what assertion I can make in this test case?

One bigger question: sorry, you slightly lost me at Gradle. Would I be building the Java SDK (cloning their repo and so forth) using Gradle (presumably with my tiny local change) and then feeding the version of the thing I built into the build.sbt of Otel4s? (and why gradle publishToMavenLocal and not use Maven? Just a noob question of mine, I only have some experience with Maven and none with Gradle)

[edit: footnotes]
1: all fine now, I was happy with my dinner tonight, my stomach less so
2: lazy because I didn't fix the upstream problem first

@iRevive
Copy link
Contributor

iRevive commented May 23, 2023

Until the upstream 'toString' of noop SDK is addressed, can we detect the noop instance by comparing it with OpenTelemetry.noop()?

For example:

val isNoop = jOtel == JOpenTelemetry.noop()

new Otel4s { 
  def toString = if (isNoop) "Noop - and something informative there if we want :)" else jOtel.toString()
}

One small question out of this is that I don't have a clean way of asserting that the output equals some expected string

We can dynamically inject value for the telemetry.sdk.version attribute. We use BuildInfo sbt plugin to collect build details during compilation.

And that's how we configure it https://github.com/typelevel/otel4s/blob/main/build.sbt#L163-L167. Currently, we collect only openTelemetrySdkVersion, and that's exactly what we need.

Then, you can inject BuildInfo.openTelemetrySdkVersion into the template string:

s"OpenTelemetrySdk{... telemetry.sdk.version=\"${BuildInfo.openTelemetrySdkVersion}\"}} ..."

We do a similar trick in a different test - https://github.com/typelevel/otel4s/blob/main/java/metrics/src/test/scala/org/typelevel/otel4s/java/metrics/CounterSuite.scala#L56

NoopLogRecordProcessor@{insert-hex-here}

This one is more complicated. As Ross suggested, we can contribute a meaningful toString of the NoopLogRecordProcessor upstream.

For example, NoopTextMapPropagator has a custom toString, while NoopLogRecordProcessor does not.

Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small question out of this is that I don't have a clean way of asserting that the output equals some expected string, because (e.g.) NoopLogRecordProcessor@{insert-hex-here} (that is part of the toString output) will always have some different hex code. Any advice on what assertion I can make in this test case?

Instead of checking for exact equality, you can do something like:

val got = (...).toString
assert(clue(got).startsWith("NoopLogRecordProcessor"))

(Anything wrapped in clue(...) will be printed out if the assert fails, to give a "clue" as to what went wrong.)


One bigger question: sorry, you slightly lost me at Gradle. Would I be building the Java SDK (cloning their repo and so forth) using Gradle (presumably with my tiny local change) and then feeding the version of the thing I built into the build.sbt of Otel4s?

Yup exactly, this is what Ross was getting at. Personally I don't think it's worth the fuss (sorry Ross ;) since it's hard to go wrong with a toString, so there's little to gain by further testing it in our own codebase. But in theory it shouldn't be too hard to do.

(and why gradle publishToMavenLocal and not use Maven? Just a noob question of mine, I only have some experience with Maven and none with Gradle)

Hmm, "Maven" is a bit over-loaded. It is actually 2 (or 3 ...) things:

  1. a build tool
  2. a repository format for publishing artifacts
  3. Maven Central, a public repository where effectively the entire Java ecosystem publishes/retrieves artifacts

In this case, Gradle is the build tool, and publishToMavenLocal publishes it to your computer's local, Maven-formatted repository. You can't use the Maven build tool, because the Java OTel SDK repository is using the Gradle build tool (similarly, we are using the sbt build tool instead of the Maven build tool).

Comment on lines 24 to 27
test("JOtelSDK toString returns useful info") {
assertEquals(
JOpenTelemetrySdk.builder().build().toString(),
"OpenTelemetrySdk{tracerProvider=SdkTracerProvider{clock=SystemClock{}, idGenerator=RandomIdGenerator{}, resource=Resource{schemaUrl=null, attributes={service.name=\"unknown_service:java\", telemetry.sdk.language=\"java\", telemetry.sdk.name=\"opentelemetry\", telemetry.sdk.version=\"1.26.0\"}}, spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647}, sampler=ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler,remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler,localParentNotSampled:AlwaysOffSampler}, spanProcessor=NoopSpanProcessor{}}, meterProvider=SdkMeterProvider{clock=SystemClock{}, resource=Resource{schemaUrl=null, attributes={service.name=\"unknown_service:java\", telemetry.sdk.language=\"java\", telemetry.sdk.name=\"opentelemetry\", telemetry.sdk.version=\"1.26.0\"}}, metricReaders=[], views=[]}, loggerProvider=SdkLoggerProvider{clock=SystemClock{}, resource=Resource{schemaUrl=null, attributes={service.name=\"unknown_service:java\", telemetry.sdk.language=\"java\", telemetry.sdk.name=\"opentelemetry\", telemetry.sdk.version=\"1.26.0\"}}, logLimits=LogLimits{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647}, logRecordProcessor=io.opentelemetry.sdk.logs.NoopLogRecordProcessor@59364403}, propagators=DefaultContextPropagators{textMapPropagator=NoopTextMapPropagator}}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this is something 😂

As a next step, can we adjust the test to build an Otel4s[IO] with the JOpenTelemetrySdk and check its toString? Currently this test is not exercising otel4s code at all 😛

@sherriesyt sherriesyt marked this pull request as ready for review May 31, 2023 01:35
Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 lgtm!

@iRevive
Copy link
Contributor

iRevive commented May 31, 2023

Eventually, we will get a better toString from the upstream: open-telemetry/opentelemetry-java#5493.

@iRevive
Copy link
Contributor

iRevive commented May 31, 2023

I'm happy with the current approach, thanks @sherriesyt!

@sherriesyt
Copy link
Contributor Author

Thanks @iRevive for the feedback & for improving the Java toString! :)

@armanbilge armanbilge merged commit b7e17ff into typelevel:main Jun 1, 2023
@rossabaker
Copy link
Member

Personally I don't think it's worth the fuss (sorry Ross ;) since it's hard to go wrong with a toString, so there's little to gain by further testing it in our own codebase. But in theory it shouldn't be too hard to do.

Yeah, I don't want to overspecify the upstream library's toString in our own tests. My golden rule comment was wanting to make sure that any improvements we make happen upstream, like the one Maksym linked. I think we're all good here.

@sherriesyt sherriesyt deleted the issue-188 branch June 4, 2023 05:04
chr12c pushed a commit to chr12c/otel4s that referenced this pull request Jun 26, 2023
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.

Basic reporting of the OpenTelemetry configuration
4 participants