diff --git a/exporters/common/build.gradle.kts b/exporters/common/build.gradle.kts
index a589816d2a2..48f610282a3 100644
--- a/exporters/common/build.gradle.kts
+++ b/exporters/common/build.gradle.kts
@@ -7,7 +7,7 @@ plugins {
description = "OpenTelemetry Exporter Common"
otelJava.moduleName.set("io.opentelemetry.exporter.internal")
-otelJava.osgiOptionalPackages.set(listOf("com.fasterxml.jackson.core", "com.google.common.io", "io.opentelemetry.api.incubator.config", "io.opentelemetry.sdk.autoconfigure.spi"))
+otelJava.osgiOptionalPackages.set(listOf("com.google.common.io", "io.opentelemetry.api.incubator.config", "io.opentelemetry.sdk.autoconfigure.spi"))
// sun.misc, io.grpc, and org.jspecify are not OSGi bundles and have no package versioning; must use unversioned optional.
otelJava.osgiUnversionedOptionalPackages.set(listOf("sun.misc", "io.grpc", "org.jspecify.annotations"))
// This bundle's exporters load sender implementations via SPI.
@@ -69,9 +69,6 @@ dependencies {
annotationProcessor("com.google.auto.value:auto-value")
- // We include helpers shared by gRPC exporters but do not want to impose these
- // dependency on all of our consumers.
- compileOnly("com.fasterxml.jackson.core:jackson-core")
// sun.misc.Unsafe from the JDK isn't found by the compiler, we provide our own trimmed down
// version that we can compile against.
compileOnly("io.grpc:grpc-stub")
diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonBufferedEncoder.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonBufferedEncoder.java
new file mode 100644
index 00000000000..b679893ed17
--- /dev/null
+++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonBufferedEncoder.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.internal.marshal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+
+/**
+ * A minimal JSON encoder that serializes directly to an {@link OutputStream} as UTF-8, buffering
+ * writes and implementing only the subset of JSON generation {@link JsonSerializer} needs so OTLP
+ * JSON serialization has no third party dependency. Method names mirror the Jackson {@code
+ * JsonGenerator} that previously backed {@link JsonSerializer}.
+ *
+ *
The caller is responsible for structural validity (matching braces); this class only inserts
+ * separators between members. {@link #writeRaw(String)} writes verbatim without touching separator
+ * state, which {@link MarshalerUtil#preserializeJsonFields(Marshaler)} relies on.
+ *
+ *
Nesting depth is capped at {@value #MAX_NESTING_DEPTH}. Attempting to nest deeper throws
+ * {@link IOException} rather than {@link StackOverflowError}, which matters for recursive OTLP
+ * structures such as cyclic {@code Value} bodies that could otherwise DoS an exporter. The cap is
+ * intentionally conservative (Jackson 3's default is 500). Real OTLP payloads nest only a handful
+ * of levels, and it is easier to relax the cap later than to tighten it.
+ */
+final class JsonBufferedEncoder {
+
+ static final int MAX_NESTING_DEPTH = 100;
+
+ private static final byte[] TRUE = {'t', 'r', 'u', 'e'};
+ private static final byte[] FALSE = {'f', 'a', 'l', 's', 'e'};
+ private static final byte[] HEX = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ private final OutputStream out;
+ private final byte[] buffer = new byte[4096];
+ private int pos;
+
+ // Whether any member has been written at each nesting depth; depth 0 is the implicit root. The
+ // array grows on demand in push(), so it does not cap nesting depth.
+ private boolean[] hasMembers = new boolean[16];
+ private int depth;
+ // True immediately after a field name has been written, meaning the next value is that field's
+ // value and must not be preceded by a comma.
+ private boolean afterKey;
+
+ JsonBufferedEncoder(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeStartObject() throws IOException {
+ beforeValue();
+ writeByte((byte) '{');
+ push();
+ }
+
+ void writeEndObject() throws IOException {
+ pop();
+ writeByte((byte) '}');
+ }
+
+ void writeObjectFieldStart(String name) throws IOException {
+ writeFieldName(name);
+ writeStartObject();
+ }
+
+ void writeArrayFieldStart(String name) throws IOException {
+ writeFieldName(name);
+ beforeValue();
+ writeByte((byte) '[');
+ push();
+ }
+
+ void writeEndArray() throws IOException {
+ pop();
+ writeByte((byte) ']');
+ }
+
+ void writeFieldName(String name) throws IOException {
+ if (hasMembers[depth]) {
+ writeByte((byte) ',');
+ }
+ hasMembers[depth] = true;
+ writeQuoted(name);
+ writeByte((byte) ':');
+ afterKey = true;
+ }
+
+ void writeStringField(String name, String value) throws IOException {
+ writeFieldName(name);
+ writeString(value);
+ }
+
+ void writeBooleanField(String name, boolean value) throws IOException {
+ writeFieldName(name);
+ beforeValue();
+ writeRawBytes(value ? TRUE : FALSE);
+ }
+
+ void writeNumberField(String name, int value) throws IOException {
+ writeFieldName(name);
+ beforeValue();
+ writeAscii(Integer.toString(value));
+ }
+
+ void writeNumberField(String name, double value) throws IOException {
+ writeFieldName(name);
+ writeNumber(value);
+ }
+
+ void writeBinaryField(String name, byte[] value) throws IOException {
+ writeFieldName(name);
+ beforeValue();
+ writeByte((byte) '"');
+ writeRawBytes(Base64.getEncoder().encode(value));
+ writeByte((byte) '"');
+ }
+
+ void writeString(String value) throws IOException {
+ beforeValue();
+ writeQuoted(value);
+ }
+
+ /**
+ * Writes a JSON string value from already UTF-8 encoded bytes, avoiding a decode-then-re-encode
+ * round trip. ASCII bytes are escaped as needed; multi-byte UTF-8 sequences pass through
+ * verbatim.
+ */
+ void writeUtf8String(byte[] utf8Bytes) throws IOException {
+ beforeValue();
+ writeByte((byte) '"');
+ for (byte b : utf8Bytes) {
+ if (b >= 0) {
+ writeEscapedAscii(b); // ASCII, may need escaping
+ } else {
+ writeByte(b); // multi-byte UTF-8, never escapable
+ }
+ }
+ writeByte((byte) '"');
+ }
+
+ void writeNumber(double value) throws IOException {
+ beforeValue();
+ // proto3 JSON encodes the non-finite values as quoted strings; a bare NaN/Infinity is not valid
+ // JSON. This matches io.opentelemetry.api.common.JsonEncoding.
+ if (Double.isNaN(value)) {
+ writeAscii("\"NaN\"");
+ } else if (Double.isInfinite(value)) {
+ writeAscii(value > 0 ? "\"Infinity\"" : "\"-Infinity\"");
+ } else {
+ writeAscii(Double.toString(value));
+ }
+ }
+
+ /** Writes pre-serialized JSON verbatim, without updating separator state. */
+ void writeRaw(String raw) throws IOException {
+ writeRawBytes(raw.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /**
+ * Drains buffered bytes to the underlying stream. Like {@code ProtoSerializer}, it neither
+ * flushes nor closes the underlying stream; the caller owns its lifecycle.
+ */
+ void close() throws IOException {
+ try {
+ if (pos > 0) {
+ out.write(buffer, 0, pos);
+ pos = 0;
+ }
+ } catch (IOException e) {
+ // In try-with-resources, draining may rethrow the same exception that failed the body; wrap
+ // it so re-throwing doesn't trigger an IllegalArgumentException from illegal
+ // self-suppression.
+ throw new IOException(e);
+ }
+ }
+
+ private void beforeValue() throws IOException {
+ if (afterKey) {
+ afterKey = false;
+ return;
+ }
+ if (hasMembers[depth]) {
+ writeByte((byte) ',');
+ }
+ hasMembers[depth] = true;
+ }
+
+ private void push() throws IOException {
+ if (depth == MAX_NESTING_DEPTH) {
+ throw new IOException(
+ "JSON nesting depth exceeds maximum allowed (" + MAX_NESTING_DEPTH + ")");
+ }
+ depth++;
+ if (depth == hasMembers.length) {
+ hasMembers = Arrays.copyOf(hasMembers, hasMembers.length * 2);
+ }
+ hasMembers[depth] = false;
+ }
+
+ private void pop() {
+ depth--;
+ }
+
+ /** Writes a quoted, escaped JSON string, encoding {@code value} as UTF-8. */
+ private void writeQuoted(String value) throws IOException {
+ writeByte((byte) '"');
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char c = value.charAt(i);
+ if (c < 0x80) {
+ writeEscapedAscii((byte) c);
+ } else if (c < 0x800) {
+ writeByte((byte) (0xC0 | (c >> 6)));
+ writeByte((byte) (0x80 | (c & 0x3F)));
+ } else if (Character.isHighSurrogate(c) && i + 1 < length) {
+ char low = value.charAt(i + 1);
+ if (Character.isLowSurrogate(low)) {
+ int codePoint = Character.toCodePoint(c, low);
+ writeByte((byte) (0xF0 | (codePoint >> 18)));
+ writeByte((byte) (0x80 | ((codePoint >> 12) & 0x3F)));
+ writeByte((byte) (0x80 | ((codePoint >> 6) & 0x3F)));
+ writeByte((byte) (0x80 | (codePoint & 0x3F)));
+ i++;
+ } else {
+ writeByte((byte) '?');
+ }
+ } else if (Character.isSurrogate(c)) {
+ // Unpaired surrogate; emit a replacement to keep the output valid UTF-8.
+ writeByte((byte) '?');
+ } else {
+ writeByte((byte) (0xE0 | (c >> 12)));
+ writeByte((byte) (0x80 | ((c >> 6) & 0x3F)));
+ writeByte((byte) (0x80 | (c & 0x3F)));
+ }
+ }
+ writeByte((byte) '"');
+ }
+
+ /** Writes a single ASCII byte (0x00-0x7F), escaping it if required by JSON. */
+ private void writeEscapedAscii(byte b) throws IOException {
+ switch (b) {
+ case '"':
+ writeByte((byte) '\\');
+ writeByte((byte) '"');
+ return;
+ case '\\':
+ writeByte((byte) '\\');
+ writeByte((byte) '\\');
+ return;
+ case '\b':
+ writeByte((byte) '\\');
+ writeByte((byte) 'b');
+ return;
+ case '\f':
+ writeByte((byte) '\\');
+ writeByte((byte) 'f');
+ return;
+ case '\n':
+ writeByte((byte) '\\');
+ writeByte((byte) 'n');
+ return;
+ case '\r':
+ writeByte((byte) '\\');
+ writeByte((byte) 'r');
+ return;
+ case '\t':
+ writeByte((byte) '\\');
+ writeByte((byte) 't');
+ return;
+ default:
+ if (b < 0x20) {
+ writeByte((byte) '\\');
+ writeByte((byte) 'u');
+ writeByte((byte) '0');
+ writeByte((byte) '0');
+ writeByte(HEX[(b >> 4) & 0xF]);
+ writeByte(HEX[b & 0xF]);
+ } else {
+ writeByte(b);
+ }
+ }
+ }
+
+ /** Writes the bytes of an ASCII-only string such as a formatted number. */
+ private void writeAscii(String value) throws IOException {
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ writeByte((byte) value.charAt(i));
+ }
+ }
+
+ private void writeRawBytes(byte[] bytes) throws IOException {
+ int offset = 0;
+ int remaining = bytes.length;
+ while (remaining > 0) {
+ if (pos == buffer.length) {
+ out.write(buffer, 0, pos);
+ pos = 0;
+ }
+ int chunk = Math.min(remaining, buffer.length - pos);
+ System.arraycopy(bytes, offset, buffer, pos, chunk);
+ pos += chunk;
+ offset += chunk;
+ remaining -= chunk;
+ }
+ }
+
+ private void writeByte(byte b) throws IOException {
+ if (pos == buffer.length) {
+ out.write(buffer, 0, pos);
+ pos = 0;
+ }
+ buffer[pos++] = b;
+ }
+}
diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java
index 62b391c8905..1d89aeba02f 100644
--- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java
+++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/JsonSerializer.java
@@ -5,201 +5,183 @@
package io.opentelemetry.exporter.internal.marshal;
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonGenerator;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
import java.util.List;
final class JsonSerializer extends Serializer {
- private static final JsonFactory JSON_FACTORY = new JsonFactory();
+ private final JsonBufferedEncoder encoder;
- private final JsonGenerator generator;
-
- JsonSerializer(OutputStream output) throws IOException {
- this(JSON_FACTORY.createGenerator(output));
- }
-
- JsonSerializer(JsonGenerator generator) {
- this.generator = generator;
+ JsonSerializer(OutputStream output) {
+ this.encoder = new JsonBufferedEncoder(output);
}
@Override
protected void writeTraceId(ProtoFieldInfo field, String traceId) throws IOException {
- generator.writeStringField(field.getJsonName(), traceId);
+ encoder.writeStringField(field.getJsonName(), traceId);
}
@Override
protected void writeSpanId(ProtoFieldInfo field, String spanId) throws IOException {
- generator.writeStringField(field.getJsonName(), spanId);
+ encoder.writeStringField(field.getJsonName(), spanId);
}
@Override
public void writeBool(ProtoFieldInfo field, boolean value) throws IOException {
- generator.writeBooleanField(field.getJsonName(), value);
+ encoder.writeBooleanField(field.getJsonName(), value);
}
@Override
protected void writeEnum(ProtoFieldInfo field, ProtoEnumInfo enumValue) throws IOException {
- generator.writeNumberField(field.getJsonName(), enumValue.getEnumNumber());
+ encoder.writeNumberField(field.getJsonName(), enumValue.getEnumNumber());
}
@Override
protected void writeUint32(ProtoFieldInfo field, int value) throws IOException {
- generator.writeNumberField(field.getJsonName(), value);
+ encoder.writeNumberField(field.getJsonName(), value);
}
@Override
protected void writeSInt32(ProtoFieldInfo field, int value) throws IOException {
- generator.writeNumberField(field.getJsonName(), value);
+ encoder.writeNumberField(field.getJsonName(), value);
}
@Override
protected void writeint32(ProtoFieldInfo field, int value) throws IOException {
- generator.writeNumberField(field.getJsonName(), value);
+ encoder.writeNumberField(field.getJsonName(), value);
}
@Override
public void writeInt64(ProtoFieldInfo field, long value) throws IOException {
- generator.writeStringField(field.getJsonName(), Long.toString(value));
+ encoder.writeStringField(field.getJsonName(), Long.toString(value));
}
@Override
protected void writeFixed64(ProtoFieldInfo field, long value) throws IOException {
- generator.writeStringField(field.getJsonName(), Long.toString(value));
+ encoder.writeStringField(field.getJsonName(), Long.toString(value));
}
@Override
protected void writeFixed64Value(long value) throws IOException {
- generator.writeString(Long.toString(value));
+ encoder.writeString(Long.toString(value));
}
@Override
protected void writeUInt64Value(long value) throws IOException {
- generator.writeString(Long.toString(value));
+ encoder.writeString(Long.toString(value));
}
@Override
public void writeUInt64(ProtoFieldInfo field, long value) throws IOException {
- generator.writeStringField(field.getJsonName(), Long.toString(value));
+ encoder.writeStringField(field.getJsonName(), Long.toString(value));
}
@Override
protected void writeFixed32(ProtoFieldInfo field, int value) throws IOException {
- generator.writeNumberField(field.getJsonName(), value);
+ encoder.writeNumberField(field.getJsonName(), value);
}
@Override
public void writeDouble(ProtoFieldInfo field, double value) throws IOException {
- generator.writeNumberField(field.getJsonName(), value);
+ encoder.writeNumberField(field.getJsonName(), value);
}
@Override
protected void writeDoubleValue(double value) throws IOException {
- generator.writeNumber(value);
+ encoder.writeNumber(value);
}
@Override
public void writeString(ProtoFieldInfo field, byte[] utf8Bytes) throws IOException {
- generator.writeFieldName(field.getJsonName());
- // Marshalers encoded String into UTF-8 bytes to optimize for binary serialization where
- // we are able to avoid the encoding process happening twice, one for size computation and one
- // for actual writing. JsonGenerator actually has a writeUTF8String that would be able to accept
- // this, but it only works when writing to an OutputStream, but not to a String like we do for
- // writing to logs. It's wasteful to take a String, convert it to bytes, and convert back to
- // the same String but we can see if this can be improved in the future.
- generator.writeString(new String(utf8Bytes, StandardCharsets.UTF_8));
+ encoder.writeFieldName(field.getJsonName());
+ // Marshalers already encoded the String to UTF-8 bytes (binary serialization needs them for
+ // both size computation and writing), so write them directly rather than decoding and
+ // re-encoding.
+ encoder.writeUtf8String(utf8Bytes);
}
@Override
public void writeString(
ProtoFieldInfo field, String string, int utf8Length, MarshalerContext context)
throws IOException {
- generator.writeFieldName(field.getJsonName());
- generator.writeString(string);
+ encoder.writeFieldName(field.getJsonName());
+ encoder.writeString(string);
}
@Override
public void writeRepeatedString(ProtoFieldInfo field, byte[][] utf8Bytes) throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
for (byte[] value : utf8Bytes) {
- // Marshalers encoded String into UTF-8 bytes to optimize for binary serialization where
- // we are able to avoid the encoding process happening twice, one for size computation and one
- // for actual writing. JsonGenerator actually has a writeUTF8String that would be able to
- // accept
- // this, but it only works when writing to an OutputStream, but not to a String like we do for
- // writing to logs. It's wasteful to take a String, convert it to bytes, and convert back to
- // the same String but we can see if this can be improved in the future.
- generator.writeString(new String(value, StandardCharsets.UTF_8));
+ // See writeString(ProtoFieldInfo, byte[]): the bytes are already UTF-8, so write directly.
+ encoder.writeUtf8String(value);
}
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
public void writeBytes(ProtoFieldInfo field, byte[] value) throws IOException {
- generator.writeBinaryField(field.getJsonName(), value);
+ encoder.writeBinaryField(field.getJsonName(), value);
}
@Override
public void writeByteBuffer(ProtoFieldInfo field, ByteBuffer value) throws IOException {
byte[] data = new byte[value.capacity()];
((ByteBuffer) value.duplicate().clear()).get(data);
- generator.writeBinaryField(field.getJsonName(), data);
+ encoder.writeBinaryField(field.getJsonName(), data);
}
@Override
protected void writeStartMessage(ProtoFieldInfo field, int protoMessageSize) throws IOException {
- generator.writeObjectFieldStart(field.getJsonName());
+ encoder.writeObjectFieldStart(field.getJsonName());
}
@Override
protected void writeEndMessage() throws IOException {
- generator.writeEndObject();
+ encoder.writeEndObject();
}
@Override
protected void writeStartRepeatedPrimitive(
ProtoFieldInfo field, int protoSizePerElement, int numElements) throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
}
@Override
protected void writeEndRepeatedPrimitive() throws IOException {
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
protected void writeStartRepeatedVarint(ProtoFieldInfo field, int payloadSize)
throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
}
@Override
protected void writeEndRepeatedVarint() throws IOException {
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
public void serializeRepeatedMessage(ProtoFieldInfo field, Marshaler[] repeatedMessage)
throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
for (Marshaler marshaler : repeatedMessage) {
writeMessageValue(marshaler);
}
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
public void serializeRepeatedMessage(
ProtoFieldInfo field, List extends Marshaler> repeatedMessage) throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
for (Marshaler marshaler : repeatedMessage) {
writeMessageValue(marshaler);
}
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
@@ -209,52 +191,52 @@ public void serializeRepeatedMessageWithContext(
StatelessMarshaler marshaler,
MarshalerContext context)
throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
for (int i = 0; i < messages.size(); i++) {
T message = messages.get(i);
- generator.writeStartObject();
+ encoder.writeStartObject();
marshaler.writeTo(this, message, context);
- generator.writeEndObject();
+ encoder.writeEndObject();
}
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
public void writeStartRepeated(ProtoFieldInfo field) throws IOException {
- generator.writeArrayFieldStart(field.getJsonName());
+ encoder.writeArrayFieldStart(field.getJsonName());
}
@Override
public void writeEndRepeated() throws IOException {
- generator.writeEndArray();
+ encoder.writeEndArray();
}
@Override
public void writeStartRepeatedElement(ProtoFieldInfo field, int protoMessageSize)
throws IOException {
- generator.writeStartObject();
+ encoder.writeStartObject();
}
@Override
public void writeEndRepeatedElement() throws IOException {
- generator.writeEndObject();
+ encoder.writeEndObject();
}
// Not a field.
void writeMessageValue(Marshaler message) throws IOException {
- generator.writeStartObject();
+ encoder.writeStartObject();
message.writeTo(this);
- generator.writeEndObject();
+ encoder.writeEndObject();
}
@Override
public void writeSerializedMessage(byte[] protoSerialized, String jsonSerialized)
throws IOException {
- generator.writeRaw(jsonSerialized);
+ encoder.writeRaw(jsonSerialized);
}
@Override
public void close() throws IOException {
- generator.close();
+ encoder.close();
}
}
diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Marshaler.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Marshaler.java
index d8ea5a9e5f4..1c65e3303ce 100644
--- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Marshaler.java
+++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Marshaler.java
@@ -5,7 +5,6 @@
package io.opentelemetry.exporter.internal.marshal;
-import com.fasterxml.jackson.core.JsonGenerator;
import io.opentelemetry.sdk.common.export.MessageWriter;
import java.io.IOException;
import java.io.OutputStream;
@@ -32,24 +31,6 @@ public final void writeJsonTo(OutputStream output) throws IOException {
}
}
- /** Marshals into the {@link JsonGenerator} in proto JSON format. */
- // Intentionally not overloading writeJsonTo(OutputStream) in order to avoid compilation
- // dependency on jackson when using writeJsonTo(OutputStream). See:
- // https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1551#discussion_r1849064365
- public final void writeJsonToGenerator(JsonGenerator output) throws IOException {
- try (JsonSerializer serializer = new JsonSerializer(output)) {
- serializer.writeMessageValue(this);
- }
- }
-
- /** Marshals into the {@link JsonGenerator} in proto JSON format and adds a newline. */
- public final void writeJsonWithNewline(JsonGenerator output) throws IOException {
- try (JsonSerializer serializer = new JsonSerializer(output)) {
- serializer.writeMessageValue(this);
- output.writeRaw('\n');
- }
- }
-
/** Returns the number of bytes this Marshaler will write in proto binary format. */
public abstract int getBinarySerializedSize();
diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java
index 494853723dc..9d7a10471f6 100644
--- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java
+++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/MarshalerUtil.java
@@ -35,19 +35,6 @@ public final class MarshalerUtil {
private static final int SPAN_ID_VALUE_SIZE =
CodedOutputStream.computeLengthDelimitedFieldSize(SpanId.getLength() / 2);
- private static final boolean JSON_AVAILABLE;
-
- static {
- boolean jsonAvailable = false;
- try {
- Class.forName("com.fasterxml.jackson.core.JsonFactory");
- jsonAvailable = true;
- } catch (ClassNotFoundException e) {
- // Not available
- }
- JSON_AVAILABLE = jsonAvailable;
- }
-
private static final byte[] EMPTY_BYTES = new byte[0];
/** Groups SDK items by resource and instrumentation scope. */
@@ -73,10 +60,6 @@ Map>> groupByResourceAndScope(
/** Preserialize into JSON format. */
public static String preserializeJsonFields(Marshaler marshaler) {
- if (!MarshalerUtil.JSON_AVAILABLE) {
- return "";
- }
-
ByteArrayOutputStream jsonBos = new ByteArrayOutputStream();
try {
marshaler.writeJsonTo(jsonBos);
@@ -85,11 +68,8 @@ public static String preserializeJsonFields(Marshaler marshaler) {
"Serialization error, this is likely a bug in OpenTelemetry.", e);
}
- // We effectively cache `writeTo`, however Jackson would not allow us to only write out
- // fields
- // which is what writeTo does. So we need to write to an object but skip the object start
- // /
- // end.
+ // Strip the surrounding { } so the cached value can be embedded as the fields of an enclosing
+ // object.
byte[] jsonBytes = jsonBos.toByteArray();
return new String(jsonBytes, 1, jsonBytes.length - 2, StandardCharsets.UTF_8);
}
diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/JsonBufferedEncoderTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/JsonBufferedEncoderTest.java
new file mode 100644
index 00000000000..b4f9df3f16e
--- /dev/null
+++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/marshal/JsonBufferedEncoderTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.exporter.internal.marshal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class JsonBufferedEncoderTest {
+
+ @FunctionalInterface
+ private interface Body {
+ void accept(JsonBufferedEncoder encoder) throws IOException;
+ }
+
+ @ParameterizedTest
+ @MethodSource("cases")
+ void writesExpectedJson(Body body, String expected) throws Exception {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ JsonBufferedEncoder encoder = new JsonBufferedEncoder(bos);
+ body.accept(encoder);
+ encoder.close();
+ assertThat(new String(bos.toByteArray(), StandardCharsets.UTF_8)).isEqualTo(expected);
+ }
+
+ // Writes a string whose UTF-8 encoding exceeds the encoder's 4096-byte internal buffer, forcing
+ // at least one intermediate flush to the underlying stream before close().
+ @Test
+ void flushesWhenBufferFills() throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 5000; i++) {
+ sb.append('a');
+ }
+ String longValue = sb.toString();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ JsonBufferedEncoder encoder = new JsonBufferedEncoder(bos);
+ encoder.writeString(longValue);
+ // Before close, the underlying stream must have received bytes from at least one mid-write
+ // flush; otherwise everything would still be sitting in the 4096-byte buffer.
+ assertThat(bos.size()).isGreaterThan(0);
+ encoder.close();
+ assertThat(new String(bos.toByteArray(), StandardCharsets.UTF_8))
+ .isEqualTo("\"" + longValue + "\"");
+ }
+
+ // Nests more objects than the initial hasMembers array capacity (16), forcing the array to grow.
+ @Test
+ void growsWhenNestingExceedsInitialCapacity() throws Exception {
+ int depth = 20;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ JsonBufferedEncoder encoder = new JsonBufferedEncoder(bos);
+ for (int i = 0; i < depth; i++) {
+ encoder.writeStartObject();
+ encoder.writeFieldName("k" + i);
+ }
+ encoder.writeString("v");
+ for (int i = 0; i < depth; i++) {
+ encoder.writeEndObject();
+ }
+ encoder.close();
+
+ StringBuilder expected = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ expected.append("{\"k").append(i).append("\":");
+ }
+ expected.append("\"v\"");
+ for (int i = 0; i < depth; i++) {
+ expected.append('}');
+ }
+ assertThat(new String(bos.toByteArray(), StandardCharsets.UTF_8))
+ .isEqualTo(expected.toString());
+ }
+
+ // Unpaired surrogates cannot be encoded as UTF-8; the encoder emits '?' in their place so the
+ // output remains valid UTF-8.
+ @Test
+ void invalidSurrogatesEmitReplacement() throws Exception {
+ // Lone low surrogate (never preceded by a high surrogate).
+ assertString(String.valueOf((char) 0xDC00), "\"?\"");
+ // High surrogate at end of string (no following char).
+ assertString(String.valueOf((char) 0xD800), "\"?\"");
+ // High surrogate followed by a non-low-surrogate BMP character (U+00E9 e-acute); the high
+ // surrogate is replaced and the following char is emitted normally.
+ assertString("" + (char) 0xD800 + (char) 0x00E9, "\"?" + (char) 0xE9 + "\"");
+ }
+
+ @Test
+ void nestingBeyondMaxDepthThrows() throws Exception {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ JsonBufferedEncoder encoder = new JsonBufferedEncoder(bos);
+ // MAX_NESTING_DEPTH successive writeStartObject calls are allowed; the next one must throw.
+ for (int i = 0; i < JsonBufferedEncoder.MAX_NESTING_DEPTH; i++) {
+ encoder.writeStartObject();
+ encoder.writeFieldName("k");
+ }
+ assertThatThrownBy(encoder::writeStartObject)
+ .isInstanceOf(IOException.class)
+ .hasMessageContaining("nesting depth");
+ }
+
+ // close() drains the buffer to the underlying stream; if that write throws, the exception is
+ // wrapped so it can propagate out of a try-with-resources without self-suppression issues.
+ @Test
+ void closeWrapsIoException() throws Exception {
+ IOException boom = new IOException("boom");
+ OutputStream failing =
+ new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ throw boom;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ throw boom;
+ }
+ };
+ JsonBufferedEncoder encoder = new JsonBufferedEncoder(failing);
+ // Write a single byte so there is buffered output for close() to attempt to flush.
+ encoder.writeString("x");
+ assertThatThrownBy(encoder::close).isInstanceOf(IOException.class).hasCause(boom);
+ }
+
+ private static void assertString(String input, String expected) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ JsonBufferedEncoder encoder = new JsonBufferedEncoder(bos);
+ encoder.writeString(input);
+ encoder.close();
+ assertThat(new String(bos.toByteArray(), StandardCharsets.UTF_8)).isEqualTo(expected);
+ }
+
+ static Stream cases() {
+ // Built with concatenation to keep the source ASCII: quote, backslash, slash, control-char
+ // shortcuts, NUL, U+001F, 'a', and the 2-/3-/4-byte UTF-8 chars U+00E9 (e-acute), U+4E16 (CJK),
+ // and U+1F600 (grinning face). Forward slash is not escaped, control characters use lowercase
+ // \\u00xx, and multi-byte UTF-8 passes through verbatim.
+ String escapeInput =
+ "\""
+ + '\\'
+ + '/'
+ + '\b'
+ + '\f'
+ + '\n'
+ + '\r'
+ + '\t'
+ + (char) 0x00
+ + (char) 0x1f
+ + 'a'
+ + (char) 0xe9
+ + (char) 0x4e16
+ + new String(Character.toChars(0x1f600));
+ String escapeExpected =
+ "\"\\\"\\\\/\\b\\f\\n\\r\\t\\u0000\\u001fa"
+ + (char) 0xe9
+ + (char) 0x4e16
+ + new String(Character.toChars(0x1f600))
+ + "\"";
+
+ // 'a', TAB, then U+00E9 (e-acute) as its two UTF-8 bytes 0xC3 0xA9.
+ byte[] utf8 = {'a', '\t', (byte) 0xc3, (byte) 0xa9};
+ String utf8Expected = "\"a\\t" + (char) 0xe9 + "\"";
+
+ return Stream.of(
+ Arguments.argumentSet(
+ "empty object",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeEndObject();
+ },
+ "{}"),
+ Arguments.argumentSet(
+ "object inserts separators between fields",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeStringField("a", "b");
+ encoder.writeNumberField("n", 1);
+ encoder.writeBooleanField("t", true);
+ encoder.writeBooleanField("f", false);
+ encoder.writeEndObject();
+ },
+ "{\"a\":\"b\",\"n\":1,\"t\":true,\"f\":false}"),
+ Arguments.argumentSet(
+ "nested objects and arrays",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeObjectFieldStart("o");
+ encoder.writeStringField("k", "v");
+ encoder.writeEndObject();
+ encoder.writeArrayFieldStart("arr");
+ encoder.writeString("x");
+ encoder.writeNumber(2.5);
+ encoder.writeEndArray();
+ encoder.writeEndObject();
+ },
+ "{\"o\":{\"k\":\"v\"},\"arr\":[\"x\",2.5]}"),
+ Arguments.argumentSet(
+ "string escaping", (Body) encoder -> encoder.writeString(escapeInput), escapeExpected),
+ Arguments.argumentSet(
+ "writeUtf8String escapes ascii and passes multi-byte through",
+ (Body) encoder -> encoder.writeUtf8String(utf8),
+ utf8Expected),
+ Arguments.argumentSet(
+ "integer boundaries",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeNumberField("min", Integer.MIN_VALUE);
+ encoder.writeNumberField("max", Integer.MAX_VALUE);
+ encoder.writeEndObject();
+ },
+ "{\"min\":-2147483648,\"max\":2147483647}"),
+ Arguments.argumentSet(
+ "exponent and signed-zero doubles",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeArrayFieldStart("d");
+ encoder.writeNumber(1.0e7);
+ encoder.writeNumber(1.0e-4);
+ encoder.writeNumber(-0.0);
+ encoder.writeNumber(0.0);
+ encoder.writeEndArray();
+ encoder.writeEndObject();
+ },
+ "{\"d\":[1.0E7,1.0E-4,-0.0,0.0]}"),
+ Arguments.argumentSet(
+ "empty base64",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeBinaryField("b", new byte[0]);
+ encoder.writeEndObject();
+ },
+ "{\"b\":\"\"}"),
+ Arguments.argumentSet(
+ "objects in array",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeArrayFieldStart("a");
+ encoder.writeStartObject();
+ encoder.writeNumberField("x", 1);
+ encoder.writeEndObject();
+ encoder.writeStartObject();
+ encoder.writeNumberField("y", 2);
+ encoder.writeEndObject();
+ encoder.writeEndArray();
+ encoder.writeEndObject();
+ },
+ "{\"a\":[{\"x\":1},{\"y\":2}]}"),
+ Arguments.argumentSet(
+ "non-finite doubles are quoted",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeArrayFieldStart("d");
+ encoder.writeNumber(Double.NaN);
+ encoder.writeNumber(Double.POSITIVE_INFINITY);
+ encoder.writeNumber(Double.NEGATIVE_INFINITY);
+ encoder.writeNumber(1.5);
+ encoder.writeEndArray();
+ encoder.writeEndObject();
+ },
+ "{\"d\":[\"NaN\",\"Infinity\",\"-Infinity\",1.5]}"),
+ Arguments.argumentSet(
+ "binary field is base64",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeBinaryField("b", new byte[] {1, 2, 3});
+ encoder.writeEndObject();
+ },
+ "{\"b\":\"AQID\"}"),
+ // Mirrors MarshalerUtil#preserializeJsonFields: pre-serialized fields written raw into an
+ // otherwise empty object, without disturbing separator state.
+ Arguments.argumentSet(
+ "writeRaw embeds verbatim",
+ (Body)
+ encoder -> {
+ encoder.writeStartObject();
+ encoder.writeObjectFieldStart("resource");
+ encoder.writeRaw("\"key\":\"val\"");
+ encoder.writeEndObject();
+ encoder.writeEndObject();
+ },
+ "{\"resource\":{\"key\":\"val\"}}"));
+ }
+}
diff --git a/exporters/logging-otlp/build.gradle.kts b/exporters/logging-otlp/build.gradle.kts
index bdec906e21c..55219c9daee 100644
--- a/exporters/logging-otlp/build.gradle.kts
+++ b/exporters/logging-otlp/build.gradle.kts
@@ -24,8 +24,6 @@ dependencies {
compileOnly(project(":api:incubator"))
compileOnly(project(":sdk-extensions:autoconfigure-spi"))
- implementation("com.fasterxml.jackson.core:jackson-core")
-
testImplementation(project(":api:incubator"))
testImplementation(project(":sdk:testing"))
testImplementation(project(":sdk-extensions:autoconfigure-spi"))
diff --git a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/JsonUtil.java b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/JsonUtil.java
deleted file mode 100644
index 0b74ed8a478..00000000000
--- a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/JsonUtil.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.exporter.logging.otlp.internal.writer;
-
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.io.SegmentedStringWriter;
-import java.io.IOException;
-
-/**
- * This class is internal and is hence not for public use. Its APIs are unstable and can change at
- * any time.
- */
-public final class JsonUtil {
-
- public static final JsonFactory JSON_FACTORY = new JsonFactory();
-
- public static JsonGenerator create(SegmentedStringWriter stringWriter) {
- try {
- return JSON_FACTORY.createGenerator(stringWriter);
- } catch (IOException e) {
- throw new IllegalStateException("Unable to create in-memory JsonGenerator, can't happen.", e);
- }
- }
-
- private JsonUtil() {}
-}
diff --git a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriter.java b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriter.java
index a95c5e0d2c1..a071dec98e3 100644
--- a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriter.java
+++ b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriter.java
@@ -5,13 +5,11 @@
package io.opentelemetry.exporter.logging.otlp.internal.writer;
-import static io.opentelemetry.exporter.logging.otlp.internal.writer.JsonUtil.JSON_FACTORY;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.sdk.common.CompletableResultCode;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -29,23 +27,25 @@ public LoggerJsonWriter(Logger logger, String type) {
this.type = type;
}
+ // toString(String) decodes straight from the internal buffer, avoiding the array copy that
+ // new String(bos.toByteArray(), UTF_8) makes. The toString(Charset) overload errorprone wants is
+ // Java 10+, but this module targets Java 8.
+ @SuppressWarnings("JdkObsolete")
@Override
public CompletableResultCode write(Marshaler exportRequest) {
- SegmentedStringWriter sw = new SegmentedStringWriter(JSON_FACTORY._getBufferRecycler());
- try (JsonGenerator gen = JsonUtil.create(sw)) {
- exportRequest.writeJsonToGenerator(gen);
- } catch (IOException e) {
- logger.log(Level.WARNING, "Unable to write OTLP JSON " + type, e);
- return CompletableResultCode.ofFailure();
- }
-
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ String json;
try {
- logger.log(Level.INFO, sw.getAndClear());
- return CompletableResultCode.ofSuccess();
+ exportRequest.writeJsonTo(bos);
+ // UTF-8 is always available; the declared UnsupportedEncodingException cannot occur.
+ json = bos.toString(StandardCharsets.UTF_8.name());
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to write OTLP JSON " + type, e);
return CompletableResultCode.ofFailure();
}
+
+ logger.log(Level.INFO, json);
+ return CompletableResultCode.ofSuccess();
}
@Override
diff --git a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriter.java b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriter.java
index a3e4e262a7e..7e17363b58a 100644
--- a/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriter.java
+++ b/exporters/logging-otlp/src/main/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriter.java
@@ -5,8 +5,6 @@
package io.opentelemetry.exporter.logging.otlp.internal.writer;
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonGenerator;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.internal.ThrottlingLogger;
@@ -21,8 +19,6 @@
*/
public class StreamJsonWriter implements JsonWriter {
- public static final JsonFactory JSON_FACTORY = new JsonFactory();
-
private static final Logger internalLogger = Logger.getLogger(StreamJsonWriter.class.getName());
private final ThrottlingLogger logger = new ThrottlingLogger(internalLogger);
@@ -38,10 +34,9 @@ public StreamJsonWriter(OutputStream originalStream, String type) {
@Override
public CompletableResultCode write(Marshaler exportRequest) {
try {
- exportRequest.writeJsonWithNewline(
- JSON_FACTORY
- .createGenerator(outputStream)
- .disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET));
+ exportRequest.writeJsonTo(outputStream);
+ outputStream.write('\n');
+ outputStream.flush();
return CompletableResultCode.ofSuccess();
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to write OTLP JSON " + type, e);
diff --git a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriterTest.java b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriterTest.java
index 680c7cc444d..566000443af 100644
--- a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriterTest.java
+++ b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/LoggerJsonWriterTest.java
@@ -9,11 +9,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import com.fasterxml.jackson.core.JsonGenerator;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.logging.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -34,9 +34,7 @@ void testToString() {
@Test
void error() throws IOException {
Marshaler marshaler = mock(Marshaler.class);
- Mockito.doThrow(new IOException("test"))
- .when(marshaler)
- .writeJsonToGenerator(any(JsonGenerator.class));
+ Mockito.doThrow(new IOException("test")).when(marshaler).writeJsonTo(any(OutputStream.class));
Logger logger = Logger.getLogger(LoggerJsonWriter.class.getName());
diff --git a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriterTest.java b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriterTest.java
index 048fcab1c17..de88db42dec 100644
--- a/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriterTest.java
+++ b/exporters/logging-otlp/src/test/java/io/opentelemetry/exporter/logging/otlp/internal/writer/StreamJsonWriterTest.java
@@ -9,7 +9,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import com.fasterxml.jackson.core.JsonGenerator;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
@@ -48,9 +47,7 @@ void testToString() throws IOException {
@Test
void errorWriting() throws IOException {
Marshaler marshaler = mock(Marshaler.class);
- Mockito.doThrow(new IOException("test"))
- .when(marshaler)
- .writeJsonWithNewline(any(JsonGenerator.class));
+ Mockito.doThrow(new IOException("test")).when(marshaler).writeJsonTo(any(OutputStream.class));
StreamJsonWriter writer = new StreamJsonWriter(System.out, "type");
writer.write(marshaler);
diff --git a/exporters/otlp/common/build.gradle.kts b/exporters/otlp/common/build.gradle.kts
index 9acc0125e7f..937f30785a3 100644
--- a/exporters/otlp/common/build.gradle.kts
+++ b/exporters/otlp/common/build.gradle.kts
@@ -30,14 +30,12 @@ dependencies {
testImplementation(project(":sdk:logs"))
testImplementation(project(":sdk:testing"))
- testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("com.google.protobuf:protobuf-java-util")
testImplementation("com.google.guava:guava")
testImplementation("io.opentelemetry.proto:opentelemetry-proto")
jmhImplementation(project(":api:incubator"))
jmhImplementation(project(":sdk:testing"))
- jmhImplementation("com.fasterxml.jackson.core:jackson-core")
jmhImplementation("io.opentelemetry.proto:opentelemetry-proto")
jmhImplementation("io.grpc:grpc-netty")
}
@@ -49,7 +47,6 @@ testing {
implementation(project(":api:incubator"))
implementation(project(":sdk:testing"))
- implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.google.protobuf:protobuf-java-util")
implementation("com.google.guava:guava")
implementation("io.opentelemetry.proto:opentelemetry-proto")
diff --git a/exporters/otlp/profiles/build.gradle.kts b/exporters/otlp/profiles/build.gradle.kts
index bbed9ee6837..ccd965fba5f 100644
--- a/exporters/otlp/profiles/build.gradle.kts
+++ b/exporters/otlp/profiles/build.gradle.kts
@@ -20,7 +20,6 @@ dependencies {
compileOnly("io.grpc:grpc-stub")
testCompileOnly("com.google.guava:guava")
- testImplementation("com.fasterxml.jackson.core:jackson-databind")
testImplementation("com.google.protobuf:protobuf-java-util")
testImplementation("io.opentelemetry.proto:opentelemetry-proto")
testImplementation(project(":exporters:otlp:testing-internal"))
diff --git a/exporters/sender/jdk/build.gradle.kts b/exporters/sender/jdk/build.gradle.kts
index d7545fab832..26cd80a9b9c 100644
--- a/exporters/sender/jdk/build.gradle.kts
+++ b/exporters/sender/jdk/build.gradle.kts
@@ -15,6 +15,4 @@ dependencies {
implementation(project(":exporters:common"))
implementation(project(":sdk:common"))
-
- testImplementation("com.fasterxml.jackson.core:jackson-core")
}
diff --git a/exporters/sender/okhttp/build.gradle.kts b/exporters/sender/okhttp/build.gradle.kts
index 29f1badf131..85aa30f3125 100644
--- a/exporters/sender/okhttp/build.gradle.kts
+++ b/exporters/sender/okhttp/build.gradle.kts
@@ -24,7 +24,6 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp")
compileOnly("io.grpc:grpc-stub")
- compileOnly("com.fasterxml.jackson.core:jackson-core")
testImplementation("com.linecorp.armeria:armeria-junit5")
}