1
1
package rabbitmq
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
6
+ "time"
5
7
8
+ "github.com/rs/zerolog/log"
6
9
"github.com/streadway/amqp"
7
10
8
11
"atomys.codes/webhooked/internal/valuable"
@@ -32,10 +35,12 @@ type config struct {
32
35
Exchange string `mapstructure:"exchange" json:"exchange"`
33
36
}
34
37
38
+ const maxAttempt = 5
39
+
35
40
// ContentType is the function for get content type used to push data in the
36
41
// storage. When no content type is defined, the default one is used instead
37
42
// Default: text/plain
38
- func (c config ) ContentType () string {
43
+ func (c * config ) ContentType () string {
39
44
if c .DefinedContentType != "" {
40
45
return c .DefinedContentType
41
46
}
@@ -67,6 +72,15 @@ func NewStorage(configRaw map[string]interface{}) (*storage, error) {
67
72
return nil , err
68
73
}
69
74
75
+ go func () {
76
+ for {
77
+ reason := <- newClient .client .NotifyClose (make (chan * amqp.Error ))
78
+ log .Warn ().Msgf ("connection to rabbitmq closed, reason: %v" , reason )
79
+
80
+ newClient .reconnect ()
81
+ }
82
+ }()
83
+
70
84
if newClient .routingKey , err = newClient .channel .QueueDeclare (
71
85
newClient .config .QueueName ,
72
86
newClient .config .Durable ,
@@ -83,26 +97,60 @@ func NewStorage(configRaw map[string]interface{}) (*storage, error) {
83
97
84
98
// Name is the function for identified if the storage config is define in the webhooks
85
99
// Run is made from external caller
86
- func (c storage ) Name () string {
100
+ func (c * storage ) Name () string {
87
101
return "rabbitmq"
88
102
}
89
103
90
104
// Push is the function for push data in the storage
91
105
// A run is made from external caller
92
106
// @param value that will be pushed
93
107
// @return an error if the push failed
94
- func (c storage ) Push (value interface {}) error {
95
- if err := c .channel .Publish (
96
- c .config .Exchange ,
97
- c .routingKey .Name ,
98
- c .config .Mandatory ,
99
- c .config .Immediate ,
100
- amqp.Publishing {
101
- ContentType : c .config .ContentType (),
102
- Body : []byte (fmt .Sprintf ("%v" , value )),
103
- }); err != nil {
104
- return err
108
+ func (c * storage ) Push (value interface {}) error {
109
+ for attempt := 0 ; attempt < maxAttempt ; attempt ++ {
110
+ err := c .channel .Publish (
111
+ c .config .Exchange ,
112
+ c .routingKey .Name ,
113
+ c .config .Mandatory ,
114
+ c .config .Immediate ,
115
+ amqp.Publishing {
116
+ ContentType : c .config .ContentType (),
117
+ Body : []byte (fmt .Sprintf ("%v" , value )),
118
+ })
119
+
120
+ if err != nil {
121
+ if errors .Is (err , amqp .ErrClosed ) {
122
+ log .Warn ().Err (err ).Msg ("connection to rabbitmq closed. reconnecting..." )
123
+ c .reconnect ()
124
+ continue
125
+ } else {
126
+ return err
127
+ }
128
+ }
129
+ return nil
105
130
}
106
131
107
- return nil
132
+ return errors .New ("max attempt to publish reached" )
133
+ }
134
+
135
+ // reconnect is the function to reconnect to the amqp server if the connection
136
+ // is lost. It will try to reconnect every seconds until it succeed to connect
137
+ func (c * storage ) reconnect () {
138
+ for {
139
+ // wait 1s for reconnect
140
+ time .Sleep (time .Second )
141
+
142
+ conn , err := amqp .Dial (c .config .DatabaseURL .First ())
143
+ if err == nil {
144
+ c .client = conn
145
+ c .channel , err = c .client .Channel ()
146
+ if err != nil {
147
+ log .Error ().Err (err ).Msg ("channel cannot be connected" )
148
+ continue
149
+ }
150
+ log .Debug ().Msg ("reconnect success" )
151
+ break
152
+ }
153
+
154
+ log .Error ().Err (err ).Msg ("reconnect failed" )
155
+ }
108
156
}
0 commit comments