diff --git a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java index d5d9d97f0f55..0ab073c1e970 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java @@ -23,6 +23,7 @@ import io.opentelemetry.semconv.ExceptionAttributes; import java.io.PrintWriter; import java.io.StringWriter; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; @@ -32,7 +33,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Function; -import java.util.function.IntFunction; import java.util.stream.Collectors; import javax.annotation.Nullable; import net.logstash.logback.marker.LogstashMarker; @@ -70,7 +70,13 @@ public final class LoggingEventMapper { private static final AttributeKey> LOG_BODY_PARAMETERS = AttributeKey.stringArrayKey("log.body.parameters"); - private static final Cache, Field> valueField = Cache.bounded(20); + private static final ClassValue valueField = + new ClassValue() { + @Override + protected FieldReader computeValue(Class type) { + return createFieldReader(type); + } + }; private final boolean captureExperimentalAttributes; private final List captureMdcAttributes; @@ -288,13 +294,13 @@ private static void captureKeyValuePairAttributes( List keyValuePairs = loggingEvent.getKeyValuePairs(); if (keyValuePairs != null) { for (KeyValuePair keyValuePair : keyValuePairs) { - captureKeyValueAttribute(attributes, keyValuePair.key, keyValuePair.value); + captureAttribute(attributes, keyValuePair.key, keyValuePair.value); } } } - private static void captureKeyValueAttribute( - AttributesBuilder attributes, Object key, Object value) { + // visible for testing + static void captureAttribute(AttributesBuilder attributes, Object key, Object value) { // empty values are not serialized if (key != null && value != null) { String keyStr = key.toString(); @@ -310,12 +316,8 @@ private static void captureKeyValueAttribute( attributes.put(keyStr, ((Number) value).doubleValue()); } else if (value.getClass().isArray()) { if (value instanceof boolean[] || value instanceof Boolean[]) { - captureKeyArrayValueAttribute( - attributes, - AttributeKey.booleanArrayKey(keyStr), - value, - Boolean[]::new, - o -> (Boolean) o); + captureArrayValueAttribute( + attributes, AttributeKey.booleanArrayKey(keyStr), value, o -> (Boolean) o); } else if (value instanceof byte[] || value instanceof Byte[] || value instanceof int[] @@ -324,36 +326,26 @@ private static void captureKeyValueAttribute( || value instanceof Long[] || value instanceof short[] || value instanceof Short[]) { - captureKeyArrayValueAttribute( - attributes, - AttributeKey.longArrayKey(keyStr), - value, - Long[]::new, - o -> ((Number) o).longValue()); + captureArrayValueAttribute( + attributes, AttributeKey.longArrayKey(keyStr), value, o -> ((Number) o).longValue()); } else if (value instanceof float[] || value instanceof Float[] || value instanceof double[] || value instanceof Double[]) { - captureKeyArrayValueAttribute( + captureArrayValueAttribute( attributes, AttributeKey.doubleArrayKey(keyStr), value, - Double[]::new, o -> ((Number) o).doubleValue()); } else { - captureKeyArrayValueAttribute( - attributes, - AttributeKey.stringArrayKey(keyStr), - value, - String[]::new, - String::valueOf); + captureArrayValueAttribute( + attributes, AttributeKey.stringArrayKey(keyStr), value, String::valueOf); } } else if (value instanceof Collection) { - captureKeyArrayValueAttribute( + captureArrayValueAttribute( attributes, AttributeKey.stringArrayKey(keyStr), ((Collection) value).toArray(), - String[]::new, String::valueOf); } else { attributes.put(getAttributeKey(keyStr), String.valueOf(value)); @@ -361,29 +353,22 @@ private static void captureKeyValueAttribute( } } - private static void captureKeyArrayValueAttribute( + private static void captureArrayValueAttribute( AttributesBuilder attributes, AttributeKey> key, Object array, - IntFunction newArray, Function extractor) { - int length = java.lang.reflect.Array.getLength(array); - T[] typedArray = newArray.apply(length); - int offset = 0; + List list = new ArrayList<>(); + int length = Array.getLength(array); for (int i = 0; i < length; i++) { - Object value = java.lang.reflect.Array.get(array, i); - // empty values are not serialized + Object value = Array.get(array, i); if (value != null) { - typedArray[i - offset] = extractor.apply(value); - } else { - offset++; + list.add(extractor.apply(value)); } } // empty lists are not serialized - if (length != offset) { - attributes.put( - key, - Arrays.asList(offset == 0 ? typedArray : Arrays.copyOf(typedArray, length - offset))); + if (!list.isEmpty()) { + attributes.put(key, list); } } @@ -520,67 +505,84 @@ private static void captureLogstashMarker( @NoMuzzle private static void captureLogstashMarkerAttributes( AttributesBuilder attributes, LogstashMarker logstashMarker) { - if (logstashMarker instanceof SingleFieldAppendingMarker) { - SingleFieldAppendingMarker singleFieldAppendingMarker = - (SingleFieldAppendingMarker) logstashMarker; - String fieldName = singleFieldAppendingMarker.getFieldName(); - Object fieldValue = extractFieldValue(singleFieldAppendingMarker); - captureKeyValueAttribute(attributes, fieldName, fieldValue); - } else if (logstashMarker instanceof MapEntriesAppendingMarker) { - MapEntriesAppendingMarker mapEntriesAppendingMarker = - (MapEntriesAppendingMarker) logstashMarker; - Map map = extractMapValue(mapEntriesAppendingMarker); - if (map != null) { - for (Map.Entry entry : map.entrySet()) { - Object key = entry.getKey(); - Object value = entry.getValue(); - captureKeyValueAttribute(attributes, key, value); - } - } + FieldReader fieldReader = valueField.get(logstashMarker.getClass()); + if (fieldReader != null) { + fieldReader.read(attributes, logstashMarker); } } - @Nullable - private static Object extractFieldValue(SingleFieldAppendingMarker singleFieldAppendingMarker) { - // ObjectAppendingMarker.fieldValue since v7.0 - // ObjectAppendingMarker.object since v3.0 - // RawJsonAppendingMarker.rawJson since v3.0 - Field field = - valueField.computeIfAbsent( - singleFieldAppendingMarker.getClass(), - clazz -> findValueField(clazz, new String[] {"fieldValue", "object", "rawJson"})); - if (field != null) { - try { - return field.get(singleFieldAppendingMarker); - } catch (IllegalAccessException e) { - // ignore - } + @NoMuzzle + private static boolean isSingleFieldAppendingMarker(Class type) { + return SingleFieldAppendingMarker.class.isAssignableFrom(type); + } + + @NoMuzzle + private static boolean isMapEntriesAppendingMarker(Class type) { + return MapEntriesAppendingMarker.class.isAssignableFrom(type); + } + + private static FieldReader createFieldReader(Class type) { + if (isSingleFieldAppendingMarker(type)) { + // ObjectAppendingMarker.fieldValue since v7.0 + // ObjectAppendingMarker.object since v3.0 + // RawJsonAppendingMarker.rawJson since v3.0 + return createStringReader(findValueField(type, "fieldValue", "object", "rawJson")); + } else if (isMapEntriesAppendingMarker(type)) { + // MapEntriesAppendingMarker.map since v3.0 + return createMapReader(findValueField(type, "map")); } return null; } + @NoMuzzle + private static String getSingleFieldAppendingMarkerName(Object logstashMarker) { + SingleFieldAppendingMarker singleFieldAppendingMarker = + (SingleFieldAppendingMarker) logstashMarker; + return singleFieldAppendingMarker.getFieldName(); + } + @Nullable - private static Map extractMapValue(MapEntriesAppendingMarker mapEntriesAppendingMarker) { - // MapEntriesAppendingMarker.map since v3.0 - Field field = - valueField.computeIfAbsent( - mapEntriesAppendingMarker.getClass(), - clazz -> findValueField(clazz, new String[] {"map"})); - if (field != null) { - try { - Object value = field.get(mapEntriesAppendingMarker); - if (value instanceof Map) { - return (Map) value; + private static FieldReader createStringReader(Field field) { + if (field == null) { + return null; + } + return (attributes, logstashMarker) -> { + String fieldName = getSingleFieldAppendingMarkerName(logstashMarker); + Object fieldValue = extractFieldValue(field, logstashMarker); + captureAttribute(attributes, fieldName, fieldValue); + }; + } + + @Nullable + private static FieldReader createMapReader(Field field) { + if (field == null) { + return null; + } + return (attributes, logstashMarker) -> { + Object fieldValue = extractFieldValue(field, logstashMarker); + if (fieldValue instanceof Map) { + Map map = (Map) fieldValue; + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + captureAttribute(attributes, key, value); } - } catch (IllegalAccessException e) { - // ignore } + }; + } + + @Nullable + private static Object extractFieldValue(Field field, Object logstashMarker) { + try { + return field.get(logstashMarker); + } catch (IllegalAccessException e) { + // ignore } return null; } @Nullable - private static Field findValueField(Class clazz, String[] fieldNames) { + private static Field findValueField(Class clazz, String... fieldNames) { for (String fieldName : fieldNames) { try { Field field = clazz.getDeclaredField(fieldName); @@ -596,17 +598,7 @@ private static Field findValueField(Class clazz, String[] fieldNames) { private static boolean supportsLogstashMarkers() { try { Class.forName("net.logstash.logback.marker.LogstashMarker"); - } catch (ClassNotFoundException e) { - return false; - } - - try { Class.forName("net.logstash.logback.marker.SingleFieldAppendingMarker"); - } catch (ClassNotFoundException e) { - return false; - } - - try { Class.forName("net.logstash.logback.marker.MapEntriesAppendingMarker"); } catch (ClassNotFoundException e) { return false; @@ -615,6 +607,10 @@ private static boolean supportsLogstashMarkers() { return true; } + private interface FieldReader { + void read(AttributesBuilder attributes, LogstashMarker logstashMarker); + } + /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. diff --git a/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapperTest.java b/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapperTest.java index 1f43c1df62cc..36da781f25c7 100644 --- a/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapperTest.java +++ b/instrumentation/logback/logback-appender-1.0/library/src/test/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapperTest.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -70,4 +71,54 @@ void testAll() { entry(AttributeKey.stringKey("key1"), "value1"), entry(AttributeKey.stringKey("key2"), "value2")); } + + @Test + void testCaptureAttributeArray() { + AttributesBuilder builder = Attributes.builder(); + + LoggingEventMapper.captureAttribute(builder, "booleanArray", new boolean[] {true}); + LoggingEventMapper.captureAttribute(builder, "BooleanArray", new Boolean[] {true}); + + LoggingEventMapper.captureAttribute(builder, "byteArray", new byte[] {2}); + LoggingEventMapper.captureAttribute(builder, "ByteArray", new Byte[] {2}); + + LoggingEventMapper.captureAttribute(builder, "shortArray", new short[] {2}); + LoggingEventMapper.captureAttribute(builder, "ShortArray", new Short[] {2}); + + LoggingEventMapper.captureAttribute(builder, "intArray", new int[] {2}); + LoggingEventMapper.captureAttribute(builder, "IntegerArray", new Integer[] {2}); + + LoggingEventMapper.captureAttribute(builder, "longArray", new long[] {2}); + LoggingEventMapper.captureAttribute(builder, "LongArray", new Long[] {2L}); + + LoggingEventMapper.captureAttribute(builder, "floatArray", new float[] {2.0f}); + LoggingEventMapper.captureAttribute(builder, "FloatArray", new Float[] {2.0f}); + + LoggingEventMapper.captureAttribute(builder, "doubleArray", new double[] {2.0}); + LoggingEventMapper.captureAttribute(builder, "DoubleArray", new Double[] {2.0}); + + LoggingEventMapper.captureAttribute(builder, "ObjectArray", new Object[] {"test"}); + LoggingEventMapper.captureAttribute(builder, "List", Collections.singletonList("test")); + LoggingEventMapper.captureAttribute(builder, "Set", Collections.singleton("test")); + + assertThat(builder.build()) + .containsOnly( + entry(AttributeKey.booleanArrayKey("booleanArray"), singletonList(Boolean.TRUE)), + entry(AttributeKey.booleanArrayKey("BooleanArray"), singletonList(Boolean.TRUE)), + entry(AttributeKey.longArrayKey("byteArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("ByteArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("shortArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("ShortArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("intArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("IntegerArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("longArray"), singletonList(2L)), + entry(AttributeKey.longArrayKey("LongArray"), singletonList(2L)), + entry(AttributeKey.doubleArrayKey("floatArray"), singletonList(2.0)), + entry(AttributeKey.doubleArrayKey("FloatArray"), singletonList(2.0)), + entry(AttributeKey.doubleArrayKey("doubleArray"), singletonList(2.0)), + entry(AttributeKey.doubleArrayKey("DoubleArray"), singletonList(2.0)), + entry(AttributeKey.stringArrayKey("ObjectArray"), singletonList("test")), + entry(AttributeKey.stringArrayKey("List"), singletonList("test")), + entry(AttributeKey.stringArrayKey("Set"), singletonList("test"))); + } }