{"record":{"id":"1b9559f4a7c2c6dc","repo":"alibaba/canal","slug":"canaleventsink-must-be-entryeventsink","errorCode":null,"errorMessage":"CanalEventSink must be EntryEventSink","messagePattern":"CanalEventSink must be EntryEventSink","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java","lineNumber":73,"sourceCode":"            long latest = emh.latestExecTime.get();\n            // execTime > now，delay显示为0\n            long d = (now >= latest) ? (now - latest) : 0;\n            delay.addMetric(emh.destLabelValues, d);\n            transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue());\n        }\n        mfs.add(delay);\n        mfs.add(transactions);\n        return mfs;\n    }\n\n    @Override\n    public void register(CanalInstance instance) {\n        final String destination = instance.getDestination();\n        EntryMetricsHolder holder = new EntryMetricsHolder();\n        holder.destLabelValues = Collections.singletonList(destination);\n        CanalEventSink sink = instance.getEventSink();\n        if (!(sink instanceof EntryEventSink)) {\n            throw new IllegalArgumentException(\"CanalEventSink must be EntryEventSink\");\n        }\n        EntryEventSink entrySink = (EntryEventSink) sink;\n        PrometheusCanalEventDownStreamHandler handler = assembleHandler(entrySink);\n        holder.latestExecTime = handler.getLatestExecuteTime();\n        holder.transactionCounter = handler.getTransactionCounter();\n        Preconditions.checkNotNull(holder.latestExecTime);\n        Preconditions.checkNotNull(holder.transactionCounter);\n        EntryMetricsHolder old = instances.put(destination, holder);\n        if (old != null) {\n            logger.warn(\"Remove stale EntryCollector for instance {}.\", destination);\n        }\n    }\n\n    @Override\n    public void unregister(CanalInstance instance) {\n        final String destination = instance.getDestination();\n        CanalEventSink sink = instance.getEventSink();\n        if (!(sink instanceof EntryEventSink)) {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java#L55-L91","documentation":"EntryCollector extracts per-entry metrics (execute time, transaction count) by attaching a PrometheusCanalEventDownStreamHandler to the instance's CanalEventSink. This only works if the sink is an EntryEventSink that exposes the handler list, so register() throws IllegalArgumentException otherwise. The instance must be wired with an EntryEventSink.","triggerScenarios":"Calling EntryCollector.register(instance) when instance.getEventSink() returns a CanalEventSink that is not an EntryEventSink (e.g. a custom or no-op sink implementation).","commonSituations":"A custom canal instance built without EntryEventSink; a deployer version where the default sink class changed; a test instance using a mock/stub sink.","solutions":["Configure the canal instance to use com.alibaba.otter.canal.sink.entry.EntryEventSink as its event sink.","If prometheus metrics are not needed, exclude the EntryCollector / disable prometheus to avoid the type check.","For custom instances, ensure getEventSink() returns an EntryEventSink-compatible sink before metrics registration."],"exampleFix":"// before\ninstance.setEventSink(new MyCustomSink()); // not EntryEventSink\nentryCollector.register(instance);\n\n// after\nEntryEventSink sink = new EntryEventSink();\ninstance.setEventSink(sink);\nentryCollector.register(instance);","handlingStrategy":"type-guard","validationCode":"CanalEventSink sink = instance.getEventSink();\nif (!(sink instanceof EntryEventSink)) {\n    throw new IllegalStateException(\"prometheus EntryCollector requires EntryEventSink, got \" + sink.getClass());\n}\nentryCollector.register(instance);","typeGuard":"boolean isEntryEventSink(CanalEventSink s) { return s instanceof EntryEventSink; }","tryCatchPattern":null,"preventionTips":["Always wire EntryEventSink when prometheus is enabled.","Disable prometheus if the instance legitimately uses a non-entry sink.","Add a startup self-check that the sink type matches the collector expectation."],"tags":["canal","prometheus","type-check","metrics","event-sink"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}