Skip to content
This repository was archived by the owner on Jun 7, 2024. It is now read-only.

Commit fc86990

Browse files
authored
Log debug topic id header for misplaced events (#1591)
1. Reuse `HeaderTag` for adding the debug header. 2. Use `EnumMap` for slightly more efficiency. 3. Log it when we detect misplaced event.
1 parent d23f3ac commit fc86990

File tree

12 files changed

+90
-57
lines changed

12 files changed

+90
-57
lines changed

api-consumption/src/main/java/org/zalando/nakadi/service/EventStream.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.slf4j.Logger;
77
import org.slf4j.LoggerFactory;
88
import org.zalando.nakadi.domain.ConsumedEvent;
9+
import org.zalando.nakadi.domain.HeaderTag;
910
import org.zalando.nakadi.domain.NakadiCursor;
1011
import org.zalando.nakadi.service.timeline.HighLevelConsumer;
1112

@@ -84,7 +85,8 @@ public void streamEvents(final Runnable checkAuthorization) {
8485
if (consumedEvents.isEmpty()) {
8586
final List<ConsumedEvent> eventsFromKafka = eventConsumer.readEvents();
8687
for (final ConsumedEvent evt : eventsFromKafka) {
87-
if (eventStreamChecks.isConsumptionBlocked(evt) || !evt.getConsumerTags().isEmpty()) {
88+
if (evt.getConsumerTags().containsKey(HeaderTag.CONSUMER_SUBSCRIPTION_ID)
89+
|| eventStreamChecks.isConsumptionBlocked(evt)) {
8890
continue;
8991
}
9092
consumedEvents.add(evt);

api-consumption/src/main/java/org/zalando/nakadi/service/subscription/StreamingContext.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,7 @@ public boolean isConsumptionBlocked(final ConsumedEvent event) {
311311
return true;
312312
}
313313
}
314-
if (event.getConsumerTags().isEmpty()) {
315-
return eventStreamChecks.isConsumptionBlocked(event);
316-
}
317-
return !checkConsumptionAllowedFromConsumerTags(event)
314+
return !isConsumptionAllowedFromConsumerTags(event)
318315
|| eventStreamChecks.isConsumptionBlocked(event);
319316
}
320317

@@ -324,8 +321,9 @@ private boolean isMisplacedEvent(final ConsumedEvent event) {
324321
try {
325322
final String actualEventTypeName = kafkaRecordDeserializer.getEventTypeName(event.getEvent());
326323
if (!expectedEventTypeName.equals(actualEventTypeName)) {
327-
LOG.warn("Consumed event for event type '{}', but expected '{}' (at position {})",
328-
actualEventTypeName, expectedEventTypeName, event.getPosition());
324+
LOG.warn("Consumed event for event type '{}', but expected '{}' (at position {}), topic id: {}",
325+
actualEventTypeName, expectedEventTypeName, event.getPosition(),
326+
event.getConsumerTags().get(HeaderTag.DEBUG_PUBLISHER_TOPIC_ID));
329327
return true;
330328
}
331329
} catch (final IOException e) {
@@ -338,11 +336,10 @@ private boolean isMisplacedEvent(final ConsumedEvent event) {
338336
return false;
339337
}
340338

341-
private boolean checkConsumptionAllowedFromConsumerTags(final ConsumedEvent event) {
342-
return event.getConsumerTags().
343-
getOrDefault(HeaderTag.CONSUMER_SUBSCRIPTION_ID,
344-
subscription.getId()).
345-
equals(subscription.getId());
339+
private boolean isConsumptionAllowedFromConsumerTags(final ConsumedEvent event) {
340+
return event.getConsumerTags()
341+
.getOrDefault(HeaderTag.CONSUMER_SUBSCRIPTION_ID, subscription.getId())
342+
.equals(subscription.getId());
346343
}
347344

348345
public CursorTokenService getCursorTokenService() {

api-consumption/src/test/java/org/zalando/nakadi/service/subscription/StreamingContextTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ public void testIncorrectEventTypeDestination() throws IOException {
230230
final var incorrectEventTypeName = "incorrect_event_type";
231231
final var supportedEventTypeName = "correct_event_type";
232232
final var subscription = new Subscription();
233+
subscription.setId(UUID.randomUUID().toString());
233234
subscription.setEventTypes(Set.of(supportedEventTypeName));
234235
final var et = new EventType();
235236
et.setCategory(EventCategory.BUSINESS);

api-publishing/src/main/java/org/zalando/nakadi/EventPublishingController.java

+18-9
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@
4242
import java.io.IOException;
4343
import java.io.InputStream;
4444
import java.util.Collections;
45-
import java.util.HashMap;
45+
import java.util.EnumMap;
4646
import java.util.List;
4747
import java.util.Map;
48+
import java.util.Optional;
4849
import java.util.UUID;
4950
import java.util.concurrent.TimeUnit;
5051
import java.util.stream.Collectors;
@@ -338,31 +339,39 @@ public static Map<HeaderTag, String> toHeaderTagMap(final String consumerString)
338339
throw new InvalidConsumerTagException(X_CONSUMER_TAG + ": is empty!");
339340
}
340341

341-
final var arr = consumerString.split(",");
342-
final Map<HeaderTag, String> result = new HashMap<>();
342+
final Map<HeaderTag, String> result = new EnumMap<>(HeaderTag.class);
343+
final String[] arr = consumerString.split(",");
343344
for (final String entry : arr) {
344345
final var tagAndValue = entry.replaceAll("\\s", "").split("=");
345346
if (tagAndValue.length != 2) {
346347
throw new InvalidConsumerTagException("header tag parameter is imbalanced, " +
347348
"expected: 2 but provided " + arr.length);
348349
}
349350

350-
final var optHeaderTag = HeaderTag.fromString(tagAndValue[0]);
351+
final Optional<HeaderTag> optHeaderTag = HeaderTag.fromString(tagAndValue[0]);
351352
if (optHeaderTag.isEmpty()) {
352353
throw new InvalidConsumerTagException("invalid header tag: " + tagAndValue[0]);
353354
}
354-
if (result.containsKey(optHeaderTag.get())) {
355-
throw new InvalidConsumerTagException("duplicate header tag: "
356-
+ optHeaderTag.get());
355+
356+
final HeaderTag headerTag = optHeaderTag.get();
357+
if (result.containsKey(headerTag)) {
358+
throw new InvalidConsumerTagException("duplicate header tag: " + headerTag);
357359
}
358-
if (optHeaderTag.get() == HeaderTag.CONSUMER_SUBSCRIPTION_ID) {
360+
361+
switch (headerTag) {
362+
case CONSUMER_SUBSCRIPTION_ID:
359363
try {
360364
UUID.fromString(tagAndValue[1]);
361365
} catch (IllegalArgumentException e) {
362366
throw new InvalidConsumerTagException("header tag value: " + tagAndValue[1] + " is not an UUID");
363367
}
368+
break;
369+
370+
default:
371+
throw new InvalidConsumerTagException("header tag unsupported: " + tagAndValue[0]);
364372
}
365-
result.put(optHeaderTag.get(), tagAndValue[1]);
373+
374+
result.put(headerTag, tagAndValue[1]);
366375
}
367376
return result;
368377
}

core-common/src/main/java/org/zalando/nakadi/domain/HeaderTag.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.zalando.nakadi.domain;
22

3-
43
import java.util.Arrays;
54
import java.util.Map;
65
import java.util.Optional;
@@ -9,13 +8,13 @@
98
import java.util.stream.Stream;
109

1110
public enum HeaderTag {
12-
CONSUMER_SUBSCRIPTION_ID;
11+
CONSUMER_SUBSCRIPTION_ID,
12+
DEBUG_PUBLISHER_TOPIC_ID;
1313

14-
private static final Map<String, HeaderTag> STRING_TO_ENUM = HeaderTag.
15-
stream().
16-
collect(Collectors.toMap(HeaderTag::name, Function.identity()));
14+
private static final Map<String, HeaderTag> STRING_TO_ENUM = HeaderTag.stream()
15+
.collect(Collectors.toMap(HeaderTag::name, Function.identity()));
1716

18-
public static Optional<HeaderTag> fromString(final String headerTag){
17+
public static Optional<HeaderTag> fromString(final String headerTag) {
1918
return Optional.ofNullable(STRING_TO_ENUM.get(headerTag.toUpperCase()));
2019
}
2120

core-common/src/main/java/org/zalando/nakadi/domain/KafkaHeaderTagSerde.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import org.apache.kafka.clients.consumer.ConsumerRecord;
55
import org.apache.kafka.clients.producer.ProducerRecord;
66

7-
import java.util.HashMap;
7+
import java.util.EnumMap;
88
import java.util.Map;
99

1010
public class KafkaHeaderTagSerde {
1111

1212
public static void serialize(final Map<HeaderTag, String> consumerTags,
1313
final ProducerRecord<byte[], byte[]> record) {
14-
consumerTags.
15-
forEach((tag, value) -> record.headers().add(tag.name(), value.getBytes(Charsets.UTF_8)));
14+
consumerTags.forEach((tag, value) ->
15+
record.headers().add(tag.name(), value.getBytes(Charsets.UTF_8)));
1616
}
1717

1818
public static Map<HeaderTag, String> deserialize(final ConsumerRecord<byte[], byte[]> record) {
19-
final var result = new HashMap<HeaderTag, String>();
19+
final Map<HeaderTag, String> result = new EnumMap<>(HeaderTag.class);
2020
record.headers().forEach(header -> {
2121
HeaderTag.fromString(header.key()).ifPresent(tag ->
2222
result.put(tag, new String(header.value(), Charsets.UTF_8))

core-common/src/main/java/org/zalando/nakadi/repository/kafka/KafkaTopicRepository.java

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.zalando.nakadi.repository.kafka;
22

3-
import com.google.common.base.Charsets;
43
import com.google.common.base.Preconditions;
54
import com.google.common.collect.ImmutableMap;
65
import com.google.common.collect.Lists;
@@ -172,12 +171,10 @@ private CompletableFuture<Exception> sendItem(
172171
item.getOwner().serialize(kafkaRecord);
173172
}
174173

175-
if (consumerTags!= null && !consumerTags.isEmpty()) {
174+
if (null != consumerTags && !consumerTags.isEmpty()) {
176175
KafkaHeaderTagSerde.serialize(consumerTags, kafkaRecord);
177176
}
178177

179-
kafkaRecord.headers().add("X-Kafka-Topic", topicId.getBytes(Charsets.UTF_8));
180-
181178
producer.send(kafkaRecord, ((metadata, exception) -> {
182179
if (null != exception) {
183180
LOG.warn("Failed to publish to kafka topic {}", topicId, exception);
@@ -420,12 +417,10 @@ public List<NakadiRecordResult> sendEvents(final String topic,
420417
nakadiRecord.getOwner().serialize(producerRecord);
421418
}
422419

423-
if( null != consumerTags) {
420+
if (null != consumerTags && !consumerTags.isEmpty()) {
424421
KafkaHeaderTagSerde.serialize(consumerTags, producerRecord);
425422
}
426423

427-
producerRecord.headers().add("X-Kafka-Topic", topic.getBytes(Charsets.UTF_8));
428-
429424
final Producer producer =
430425
kafkaFactory.takeProducer(getProducerKey(topic, nakadiRecord.getMetadata().getPartition()));
431426
producer.send(producerRecord, ((metadata, exception) -> {

core-common/src/main/java/org/zalando/nakadi/service/TracingService.java

+2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ public class TracingService {
2323

2424
private static final long BUCKET_5_KB = 5000L;
2525
private static final long BUCKET_50_KB = 50000L;
26+
2627
public static final String ERROR_DESCRIPTION = "error.description";
28+
public static final String TAG_EVENT_TYPE = "event_type";
2729

2830
public static String getSLOBucketName(final long batchSize) {
2931
if (batchSize > BUCKET_50_KB) {

core-common/src/test/java/org/zalando/nakadi/domain/KafkaHeaderTagSerializerTest.java

+20-9
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,41 @@
1212
public class KafkaHeaderTagSerializerTest {
1313

1414
private static final String SUB_ID = "16120729-4a57-4607-ad3a-d526a4590e75";
15+
private static final String TOPIC_ID = "010b65ff-7343-425d-821e-d64e014925c9";
1516

1617
@Test
1718
public void testConsumerTagSerializer() {
18-
final var consumerTags = Map.of(HeaderTag.CONSUMER_SUBSCRIPTION_ID, SUB_ID);
19+
final Map<HeaderTag, String> consumerTags = Map.of(
20+
HeaderTag.CONSUMER_SUBSCRIPTION_ID, SUB_ID,
21+
HeaderTag.DEBUG_PUBLISHER_TOPIC_ID, TOPIC_ID);
22+
1923
final ProducerRecord<byte[], byte[]> record = new ProducerRecord<>(
20-
"topic",
21-
"value".getBytes(StandardCharsets.UTF_8));
24+
"topic", "value".getBytes(StandardCharsets.UTF_8));
25+
2226
KafkaHeaderTagSerde.serialize(consumerTags, record);
2327

2428
Assert.assertEquals(SUB_ID,
2529
new String(
26-
record.headers().lastHeader(HeaderTag.CONSUMER_SUBSCRIPTION_ID.name()).
27-
value(),
30+
record.headers().lastHeader(HeaderTag.CONSUMER_SUBSCRIPTION_ID.name()).value(),
31+
Charsets.UTF_8));
32+
33+
Assert.assertEquals(TOPIC_ID,
34+
new String(
35+
record.headers().lastHeader(HeaderTag.DEBUG_PUBLISHER_TOPIC_ID.name()).value(),
2836
Charsets.UTF_8));
2937
}
3038

3139
@Test
3240
public void testConsumerTagDeserializer() {
33-
final ConsumerRecord<byte[], byte[]> record =
34-
new ConsumerRecord<>("topic", 1, 1L, "key".getBytes(), "value".getBytes());
41+
final ConsumerRecord<byte[], byte[]> record = new ConsumerRecord<>(
42+
"topic", 1, 1L, "key".getBytes(), "value".getBytes());
43+
3544
record.headers().add(HeaderTag.CONSUMER_SUBSCRIPTION_ID.name(), SUB_ID.getBytes(Charsets.UTF_8));
45+
record.headers().add(HeaderTag.DEBUG_PUBLISHER_TOPIC_ID.name(), TOPIC_ID.getBytes(Charsets.UTF_8));
46+
47+
final Map<HeaderTag, String> consumerTags = KafkaHeaderTagSerde.deserialize(record);
3648

37-
final var consumerTags = KafkaHeaderTagSerde.deserialize(record);
3849
Assert.assertEquals(consumerTags.get(HeaderTag.CONSUMER_SUBSCRIPTION_ID), SUB_ID);
50+
Assert.assertEquals(consumerTags.get(HeaderTag.DEBUG_PUBLISHER_TOPIC_ID), TOPIC_ID);
3951
}
40-
4152
}

core-services/src/main/java/org/zalando/nakadi/service/publishing/BinaryEventPublisher.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import java.io.Closeable;
3030
import java.io.IOException;
31+
import java.util.EnumMap;
3132
import java.util.List;
3233
import java.util.Map;
3334
import java.util.concurrent.TimeoutException;
@@ -104,11 +105,18 @@ private List<NakadiRecordResult> processInternal(final EventType eventType,
104105

105106
final Span publishingSpan = TracingService.buildNewSpan("publishing_to_kafka")
106107
.withTag(Tags.MESSAGE_BUS_DESTINATION.getKey(), topic)
107-
.withTag("event_type", eventType.getName())
108+
.withTag(TracingService.TAG_EVENT_TYPE, eventType.getName())
108109
.withTag("type", "binary")
109110
.start();
110111
try (Closeable ignored = TracingService.activateSpan(publishingSpan)) {
111-
return timelineService.getTopicRepository(eventType).sendEvents(topic, records, consumerTags);
112+
// DEBUG
113+
final Map<HeaderTag, String> debugConsumerTags = new EnumMap<>(HeaderTag.class);
114+
if (null != consumerTags) {
115+
debugConsumerTags.putAll(consumerTags);
116+
}
117+
debugConsumerTags.put(HeaderTag.DEBUG_PUBLISHER_TOPIC_ID, topic);
118+
// DEBUG
119+
return timelineService.getTopicRepository(eventType).sendEvents(topic, records, debugConsumerTags);
112120
} catch (final IOException ioe) {
113121
throw new InternalNakadiException("Error closing active span scope", ioe);
114122
} finally {

core-services/src/main/java/org/zalando/nakadi/service/publishing/EventPublisher.java

+12-4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
import java.io.Closeable;
5252
import java.io.IOException;
53+
import java.util.EnumMap;
5354
import java.util.List;
5455
import java.util.Map;
5556
import java.util.Optional;
@@ -63,7 +64,6 @@
6364
public class EventPublisher {
6465

6566
private static final Logger LOG = LoggerFactory.getLogger(EventPublisher.class);
66-
private static final String TAG_EVENT_TYPE = "event_type";
6767

6868
private final NakadiSettings nakadiSettings;
6969

@@ -286,7 +286,7 @@ private void validate(final List<BatchItem> batch, final EventType eventType, fi
286286
throws EventValidationException, InternalNakadiException, NoSuchEventTypeException {
287287

288288
final Tracer.SpanBuilder validationSpan = TracingService.buildNewSpan("validation")
289-
.withTag(TAG_EVENT_TYPE, eventType.getName());
289+
.withTag(TracingService.TAG_EVENT_TYPE, eventType.getName());
290290

291291
try (Closeable ignored = TracingService.withActiveSpan(validationSpan)) {
292292

@@ -320,6 +320,7 @@ private void submit(
320320
final List<BatchItem> batch, final EventType eventType,
321321
final Map<HeaderTag, String> consumerTags, final boolean delete)
322322
throws EventPublishingException, InternalNakadiException {
323+
323324
final Timeline activeTimeline = timelineService.getActiveTimeline(eventType);
324325
final String topic = activeTimeline.getTopic();
325326

@@ -334,10 +335,17 @@ private void submit(
334335

335336
final Span publishingSpan = TracingService.buildNewSpan("publishing_to_kafka")
336337
.withTag(Tags.MESSAGE_BUS_DESTINATION.getKey(), topic)
337-
.withTag(TAG_EVENT_TYPE, eventType.getName())
338+
.withTag(TracingService.TAG_EVENT_TYPE, eventType.getName())
338339
.start();
339340
try (Closeable ignored = TracingService.activateSpan(publishingSpan)) {
340-
topicRepository.syncPostBatch(topic, batch, eventType.getName(), consumerTags, delete);
341+
// DEBUG
342+
final Map<HeaderTag, String> debugConsumerTags = new EnumMap<>(HeaderTag.class);
343+
if (null != consumerTags) {
344+
debugConsumerTags.putAll(consumerTags);
345+
}
346+
debugConsumerTags.put(HeaderTag.DEBUG_PUBLISHER_TOPIC_ID, topic);
347+
// DEBUG
348+
topicRepository.syncPostBatch(topic, batch, eventType.getName(), debugConsumerTags, delete);
341349
} catch (final EventPublishingException epe) {
342350
publishingSpan.log(epe.getMessage());
343351
throw epe;

core-services/src/test/java/org/zalando/nakadi/service/publishing/EventPublisherTest.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ public void whenPartitionIsUnavailable207IsReportedBinary() throws Exception {
212212

213213
final List<NakadiRecord> records = Collections.singletonList(nakadiRecord);
214214
final List<NakadiRecordResult> publishResult = eventPublisher.publish(eventType, records, null);
215-
Mockito.verify(topicRepository).sendEvents(ArgumentMatchers.eq(topic), ArgumentMatchers.eq(records), eq(null));
215+
216+
Mockito.verify(topicRepository).sendEvents(ArgumentMatchers.eq(topic), ArgumentMatchers.eq(records), any());
216217

217218
Assert.assertNotEquals(NakadiRecordResult.Status.SUCCEEDED, publishResult.get(0).getStatus());
218219
Assert.assertEquals("1", publishResult.get(0).getMetadata().getPartition());
@@ -728,10 +729,10 @@ public void testAvroEventWasSerialized() throws Exception {
728729
.thenReturn("1");
729730

730731
final NakadiRecord nakadiRecord = mockNakadiRecord();
731-
732732
final List<NakadiRecord> records = Collections.singletonList(nakadiRecord);
733733
eventPublisher.publish(eventType, records, null);
734-
Mockito.verify(topicRepository).sendEvents(ArgumentMatchers.eq(topic), ArgumentMatchers.eq(records), eq(null));
734+
735+
Mockito.verify(topicRepository).sendEvents(ArgumentMatchers.eq(topic), ArgumentMatchers.eq(records), any());
735736
}
736737

737738
@Test
@@ -929,7 +930,7 @@ private void mockFailedBinaryWriteToKafka() {
929930
new TimeoutException()));
930931
}
931932
return resps;
932-
}).when(topicRepository).sendEvents(any(), any(), eq(null));
933+
}).when(topicRepository).sendEvents(any(), any(), any());
933934
}
934935

935936
}

0 commit comments

Comments
 (0)