@@ -16,6 +16,7 @@ package webhook
1616
1717import (
1818 "bytes"
19+ "context"
1920 "crypto/sha256"
2021 "encoding/base64"
2122 "sync"
@@ -25,6 +26,7 @@ import (
2526 "github.com/hashicorp/go-retryablehttp"
2627 "go.uber.org/atomic"
2728 "google.golang.org/protobuf/encoding/protojson"
29+ "google.golang.org/protobuf/types/known/timestamppb"
2830
2931 "github.com/livekit/protocol/auth"
3032 "github.com/livekit/protocol/livekit"
@@ -37,11 +39,12 @@ const (
3739
3840type URLNotifierParams struct {
3941 HTTPClientParams
40- Logger logger.Logger
41- QueueSize int
42- URL string
43- APIKey string
44- APISecret string
42+ Logger logger.Logger
43+ QueueSize int
44+ URL string
45+ APIKey string
46+ APISecret string
47+ FieldsHook func (whi * livekit.WebhookInfo )
4548}
4649
4750type HTTPClientParams struct {
@@ -56,11 +59,12 @@ const defaultQueueSize = 100
5659// URLNotifier is a QueuedNotifier that sends a POST request to a Webhook URL.
5760// It will retry on failure, and will drop events if notification fall too far behind
5861type URLNotifier struct {
59- mu sync.RWMutex
60- params URLNotifierParams
61- client * retryablehttp.Client
62- dropped atomic.Int32
63- pool core.QueuePool
62+ mu sync.RWMutex
63+ params URLNotifierParams
64+ client * retryablehttp.Client
65+ dropped atomic.Int32
66+ pool core.QueuePool
67+ processedHook func (ctx context.Context , whi * livekit.WebhookInfo )
6468}
6569
6670func NewURLNotifier (params URLNotifierParams ) * URLNotifier {
@@ -93,7 +97,6 @@ func NewURLNotifier(params URLNotifierParams) *URLNotifier {
9397 n .pool = core .NewQueuePool (numWorkers , core.QueueWorkerParams {
9498 QueueSize : params .QueueSize ,
9599 DropWhenFull : true ,
96- OnDropped : func () { n .dropped .Inc () },
97100 })
98101 return n
99102}
@@ -105,25 +108,76 @@ func (n *URLNotifier) SetKeys(apiKey, apiSecret string) {
105108 n .params .APISecret = apiSecret
106109}
107110
108- func (n * URLNotifier ) QueueNotify (event * livekit.WebhookEvent ) error {
111+ func (n * URLNotifier ) RegisterProcessedHook (hook func (ctx context.Context , whi * livekit.WebhookInfo )) {
112+ n .mu .Lock ()
113+ defer n .mu .Unlock ()
114+ n .processedHook = hook
115+ }
116+
117+ func (n * URLNotifier ) getProcessedHook () func (ctx context.Context , whi * livekit.WebhookInfo ) {
118+ n .mu .RLock ()
119+ defer n .mu .RUnlock ()
120+ return n .processedHook
121+ }
122+
123+ func (n * URLNotifier ) QueueNotify (ctx context.Context , event * livekit.WebhookEvent ) error {
109124 enqueuedAt := time .Now ()
110125
111- n .pool .Submit (n .eventKey (event ), func () {
112- fields := logFields (event )
113- fields = append ( fields ,
114- "url" , n . params . URL ,
115- "queueDuration" , time . Since ( enqueuedAt ),
116- )
117- sentStart := time .Now ()
126+ if ! n .pool .Submit (n .eventKey (event ), func () {
127+ fields := logFields (event , n . params . URL )
128+
129+ queueDuration := time . Since ( enqueuedAt )
130+ fields = append ( fields , "queueDuration" , queueDuration )
131+
132+ sendStart := time .Now ()
118133 err := n .send (event )
119- fields = append (fields , "sendDuration" , time .Since (sentStart ))
134+ sendDuration := time .Since (sendStart )
135+ fields = append (fields , "sendDuration" , sendDuration )
120136 if err != nil {
121137 n .params .Logger .Warnw ("failed to send webhook" , err , fields ... )
122138 n .dropped .Add (event .NumDropped + 1 )
123139 } else {
124140 n .params .Logger .Infow ("sent webhook" , fields ... )
125141 }
126- })
142+ if ph := n .getProcessedHook (); ph != nil {
143+ whi := webhookInfo (
144+ event ,
145+ enqueuedAt ,
146+ queueDuration ,
147+ sendStart ,
148+ sendDuration ,
149+ n .params .URL ,
150+ false ,
151+ err ,
152+ )
153+ if n .params .FieldsHook != nil {
154+ n .params .FieldsHook (whi )
155+ }
156+ ph (ctx , whi )
157+ }
158+ }) {
159+ n .dropped .Inc ()
160+
161+ fields := logFields (event , n .params .URL )
162+ n .params .Logger .Infow ("dropped webhook" , fields ... )
163+
164+ if ph := n .getProcessedHook (); ph != nil {
165+ whi := webhookInfo (
166+ event ,
167+ time.Time {},
168+ 0 ,
169+ time.Time {},
170+ 0 ,
171+ n .params .URL ,
172+ true ,
173+ nil ,
174+ )
175+ if n .params .FieldsHook != nil {
176+ n .params .FieldsHook (whi )
177+ }
178+ ph (ctx , whi )
179+ }
180+ }
127181 return nil
128182}
129183
@@ -197,12 +251,13 @@ type logAdapter struct{}
197251
198252func (l * logAdapter ) Printf (string , ... interface {}) {}
199253
200- func logFields (event * livekit.WebhookEvent ) []interface {} {
254+ func logFields (event * livekit.WebhookEvent , url string ) []interface {} {
201255 fields := make ([]interface {}, 0 , 20 )
202256 fields = append (fields ,
203257 "event" , event .Event ,
204258 "id" , event .Id ,
205259 "webhookTime" , event .CreatedAt ,
260+ "url" , url ,
206261 )
207262
208263 if event .Room != nil {
@@ -217,6 +272,11 @@ func logFields(event *livekit.WebhookEvent) []interface{} {
217272 "pID" , event .Participant .Sid ,
218273 )
219274 }
275+ if event .Track != nil {
276+ fields = append (fields ,
277+ "trackID" , event .Track .Sid ,
278+ )
279+ }
220280 if event .EgressInfo != nil {
221281 fields = append (fields ,
222282 "egressID" , event .EgressInfo .EgressId ,
@@ -239,3 +299,65 @@ func logFields(event *livekit.WebhookEvent) []interface{} {
239299 }
240300 return fields
241301}
302+
303+ func webhookInfo (
304+ event * livekit.WebhookEvent ,
305+ queuedAt time.Time ,
306+ queueDuration time.Duration ,
307+ sentAt time.Time ,
308+ sendDuration time.Duration ,
309+ url string ,
310+ isDropped bool ,
311+ sendError error ,
312+ ) * livekit.WebhookInfo {
313+ whi := & livekit.WebhookInfo {
314+ EventId : event .Id ,
315+ Event : event .Event ,
316+ CreatedAt : timestamppb .New (time .Unix (event .CreatedAt , 0 )),
317+ QueuedAt : timestamppb .New (queuedAt ),
318+ QueueDurationNs : queueDuration .Nanoseconds (),
319+ SentAt : timestamppb .New (sentAt ),
320+ SendDurationNs : sendDuration .Nanoseconds (),
321+ Url : url ,
322+ NumDropped : event .NumDropped ,
323+ IsDropped : isDropped ,
324+ }
325+ if ! queuedAt .IsZero () {
326+ whi .QueuedAt = timestamppb .New (queuedAt )
327+ }
328+ if ! sentAt .IsZero () {
329+ whi .SentAt = timestamppb .New (sentAt )
330+ }
331+ if event .Room != nil {
332+ whi .RoomName = event .Room .Name
333+ whi .RoomId = event .Room .Sid
334+ }
335+ if event .Participant != nil {
336+ whi .ParticipantIdentity = event .Participant .Identity
337+ whi .ParticipantId = event .Participant .Sid
338+ }
339+ if event .Track != nil {
340+ whi .TrackId = event .Track .Sid
341+ }
342+ if event .EgressInfo != nil {
343+ whi .EgressId = event .EgressInfo .EgressId
344+ whi .ServiceStatus = event .EgressInfo .Status .String ()
345+ if event .EgressInfo .Error != "" {
346+ whi .ServiceErrorCode = event .EgressInfo .ErrorCode
347+ whi .ServiceError = event .EgressInfo .Error
348+ }
349+ }
350+ if event .IngressInfo != nil {
351+ whi .IngressId = event .IngressInfo .IngressId
352+ if event .IngressInfo .State != nil {
353+ whi .ServiceStatus = event .IngressInfo .State .Status .String ()
354+ if event .IngressInfo .State .Error != "" {
355+ whi .ServiceError = event .IngressInfo .State .Error
356+ }
357+ }
358+ }
359+ if sendError != nil {
360+ whi .SendError = sendError .Error ()
361+ }
362+ return whi
363+ }
0 commit comments