feat(onvif): log the topic that triggered a recording

dispatchEvent logged only rejected events, so the topic that actually
started a recording was invisible — the only way to identify it was to
enumerate every rejected topic and reason about what was left. On a
camera emitting 18 distinct topics that is not a diagnosis.

Log the Kind and topic on the dispatch path too, at debug, matching the
reject line's shape so both sides of the decision grep the same way.
This commit is contained in:
T. Tradesman
2026-07-23 11:52:51 +02:00
parent 357cc719a5
commit 57cfc90c4b
2 changed files with 48 additions and 0 deletions

View File

@@ -129,6 +129,10 @@ func dispatchEvent(ctx context.Context, ev stream.Event, configuration *models.C
if ctx.Err() != nil {
return
}
// The topic that actually started a recording is the one on-call
// needs; the reject path below already names the ones that didn't.
log.Log.Debug("onvif.dispatchEvent(): recording trigger " + ev.Kind.String() + " topic=" + ev.Topic)
dataToPass := models.MotionDataPartial{
Timestamp: time.Now().Unix(),
NumberOfChanges: 0, // ONVIF does not quantify motion area.

View File

@@ -1,12 +1,14 @@
package onvif
import (
"bytes"
"context"
"testing"
"time"
"github.com/kerberos-io/agent/machinery/src/models"
"github.com/kerberos-io/onvif/event/stream"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
@@ -79,6 +81,48 @@ func TestDispatchEvent_NonMotionKindIgnored(t *testing.T) {
}
}
// captureDebugLog redirects logrus to a buffer at debug level for the
// duration of a test and returns what was written. It mutates package
// globals, so callers must not run in parallel.
func captureDebugLog(t *testing.T) *bytes.Buffer {
t.Helper()
var buf bytes.Buffer
prevOut, prevLevel := logrus.StandardLogger().Out, logrus.GetLevel()
logrus.SetOutput(&buf)
logrus.SetLevel(logrus.DebugLevel)
t.Cleanup(func() {
logrus.SetOutput(prevOut)
logrus.SetLevel(prevLevel)
})
return &buf
}
// TestDispatchEvent_LogsTheTriggeringTopic — a dispatched event is what
// actually starts a recording, so its topic is the one an operator needs
// when a camera records for the wrong reason (or the right reason and
// nobody can prove which). Rejected events were already logged; without
// this the triggering topic is only knowable by elimination.
func TestDispatchEvent_LogsTheTriggeringTopic(t *testing.T) {
buf := captureDebugLog(t)
cfg := makeConfig("true", "true", "cam-1")
comm := makeCommunication(1)
ev := stream.Event{
Kind: stream.KindMotion,
State: stream.StateActive,
Topic: "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1",
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dispatchEvent(ctx, ev, cfg, comm)
assert.Contains(t, buf.String(), "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1",
"the dispatched event's topic must appear in the log")
assert.Contains(t, buf.String(), "Motion",
"the dispatched event's Kind must appear in the log")
}
func TestDispatchEvent_RecordingDisabled_DoesNotSend(t *testing.T) {
cfg := makeConfig("false", "true", "cam-1")
comm := makeCommunication(1)