├── LICENSE ├── README.md ├── cdc.go ├── channel_stream.go ├── go.mod ├── go.sum ├── logger.go └── types.go /LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license: http://www.opensource.org/licenses/mit-license.php 2 | 3 | Copyright (c) Vertexcover Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 9 | to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 17 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## postgresql-cdc 2 | 3 | postgresql-cdc is a simple Golang library that allows you to stream postgresql changes over a configurable streaming interface. The library comes with a `ChannelStream` that allows you to listen to postgres changes over a golanng channel. 4 | -------------------------------------------------------------------------------- /cdc.go: -------------------------------------------------------------------------------- 1 | package cdc 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/jackc/pgconn" 12 | "github.com/jackc/pglogrepl" 13 | "github.com/jackc/pgproto3/v2" 14 | "github.com/jackc/pgx/v4" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | var activeSessions = make(map[string]*session) 19 | 20 | func generateSlotName() string { 21 | return fmt.Sprintf("slot_%d", time.Now().Unix()) 22 | } 23 | 24 | func sendStatusUpdate(sess *session) error { 25 | err := pglogrepl.SendStandbyStatusUpdate( 26 | context.Background(), sess.PgConn(), 27 | pglogrepl.StandbyStatusUpdate{ 28 | WALWritePosition: sess.nextLSN, 29 | WALFlushPosition: sess.nextLSN, 30 | WALApplyPosition: sess.nextLSN, 31 | }, 32 | ) 33 | if err != nil { 34 | return err 35 | } 36 | logger.Debug("Sent Standby status message. Apply Position: %s", sess.nextLSN) 37 | sess.nextStatusDeadline = time.Now().Add(sess.statusInterval) 38 | return nil 39 | } 40 | 41 | func handleKeepAliveMessage( 42 | msg *pgproto3.CopyData, 43 | sess *session, 44 | ) error { 45 | pkm, err := pglogrepl.ParsePrimaryKeepaliveMessage(msg.Data[1:]) 46 | if err != nil { 47 | return errors.Wrap(err, "Failed while parsing primary keep alive message") 48 | } 49 | // logger.Println("Primary Keepalive Message =>", "ServerWALEnd:", pkm.ServerWALEnd, "ServerTime:", pkm.ServerTime, "ReplyRequested:", pkm.ReplyRequested) 50 | 51 | if pkm.ReplyRequested { 52 | sess.nextStatusDeadline = time.Time{} 53 | } 54 | return nil 55 | } 56 | 57 | func logWALMessage(logFile string, walMessage string) error { 58 | f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 59 | if err != nil { 60 | return errors.Wrapf(err, "failed to open log file: %s", logFile) 61 | } 62 | defer f.Close() 63 | if _, err := f.WriteString(walMessage + "\n"); err != nil { 64 | return errors.Wrap(err, "Unable to log message to file") 65 | } 66 | return nil 67 | } 68 | 69 | func handleWALMessage( 70 | msg *pgproto3.CopyData, 71 | sess *session, 72 | ) (*Wal2JsonMessage, error) { 73 | xld, err := pglogrepl.ParseXLogData(msg.Data[1:]) 74 | if err != nil { 75 | return nil, errors.Wrap(err, "Failed to parse xlog message") 76 | } 77 | logWALMessage("wal.txt", string(xld.WALData)) 78 | 79 | walMessage := &Wal2JsonMessage{} 80 | err = json.Unmarshal(xld.WALData, walMessage) 81 | if err != nil { 82 | return nil, errors.Wrap(err, "Unable to parse the wal message: ") 83 | } 84 | walMessage.WALStart = xld.WALStart 85 | return walMessage, nil 86 | } 87 | 88 | func closeSession(sess *session) error { 89 | if err := sendStatusUpdate(sess); err != nil { 90 | logger.Warn("Unable to send status update while closing session", err) 91 | } 92 | pglogrepl.DropReplicationSlot( 93 | context.Background(), sess.PgConn(), sess.slotName, pglogrepl.DropReplicationSlotOptions{}, 94 | ) 95 | delete(activeSessions, sess.slotName) 96 | sess.replConn.Close(context.Background()) 97 | return nil 98 | } 99 | 100 | func fetchMessageFromPostgres( 101 | sess *session, 102 | ) error { 103 | ctx, cancel := context.WithDeadline(context.Background(), sess.nextStatusDeadline) 104 | msg, err := sess.PgConn().ReceiveMessage(ctx) 105 | cancel() 106 | if err != nil { 107 | if pgconn.Timeout(err) { 108 | logger.Debug("Error while receiving message", err) 109 | return nil 110 | } 111 | return err 112 | } 113 | switch msg := msg.(type) { 114 | case *pgproto3.CopyData: 115 | switch msg.Data[0] { 116 | case pglogrepl.PrimaryKeepaliveMessageByteID: 117 | return handleKeepAliveMessage(msg, sess) 118 | case pglogrepl.XLogDataByteID: 119 | walMessage, err := handleWALMessage(msg, sess) 120 | if err != nil { 121 | return err 122 | } 123 | sess.connector.Send(walMessage) 124 | return nil 125 | default: 126 | return fmt.Errorf("Received unexpected message: %+v", msg) 127 | } 128 | } 129 | return nil 130 | } 131 | 132 | func streamReplicationMessages( 133 | ctx context.Context, 134 | sess *session, 135 | ) error { 136 | for { 137 | if time.Now().After(sess.nextStatusDeadline) { 138 | if err := sendStatusUpdate(sess); err != nil { 139 | logger.Error("Failed to send status update", err) 140 | } 141 | } 142 | select { 143 | case <-ctx.Done(): 144 | logger.Info("Context is cancelled, closing session") 145 | closeSession(sess) 146 | default: 147 | 148 | if err := fetchMessageFromPostgres(sess); err != nil { 149 | logger.Error("Failed while fetching message from postgres", err) 150 | } 151 | } 152 | } 153 | } 154 | 155 | func replicationSlotExists(conn *pgx.Conn, slotName string) (bool, error) { 156 | var exists bool 157 | err := conn.QueryRow( 158 | context.Background(), 159 | "SELECT EXISTS(Select 1 from pg_replication_slots where slot_name = $1)", 160 | slotName, 161 | ).Scan(&exists) 162 | 163 | if err != nil { 164 | return false, err 165 | } 166 | 167 | return exists, nil 168 | } 169 | 170 | func initiateReplication( 171 | ctx context.Context, 172 | conn *pgx.Conn, 173 | sess *session, 174 | ) error { 175 | 176 | exists, err := replicationSlotExists(conn, sess.slotName) 177 | if err != nil { 178 | return errors.Wrapf(err, "Unable to check if replication slot: %s exist", sess.slotName) 179 | } 180 | 181 | if exists { 182 | logger.Infof("Replication Slot: %s Already Exists. No need to start replication", sess.slotName) 183 | } else { 184 | 185 | if _, err := pglogrepl.CreateReplicationSlot( 186 | ctx, sess.PgConn(), sess.slotName, "wal2json", 187 | pglogrepl.CreateReplicationSlotOptions{}, 188 | ); err != nil { 189 | return errors.Wrap(err, "CreateReplicationSlot failed") 190 | } 191 | logger.Debugf("Created temporary replication slot: %s", sess.slotName) 192 | } 193 | err = pglogrepl.StartReplication( 194 | ctx, sess.PgConn(), 195 | sess.slotName, pglogrepl.LSN(sess.nextLSN), 196 | pglogrepl.StartReplicationOptions{PluginArgs: []string{ 197 | "\"include-lsn\" 'on'", 198 | "\"pretty-print\" 'off'", 199 | "\"include-timestamp\" 'on'", 200 | "\"filter-tables\" 'public.product_execution_flow, public.current_lsn_status'", 201 | }}, 202 | ) 203 | if err != nil { 204 | return errors.Wrap(err, "Start replication log failed") 205 | } 206 | logger.Infof("Logical replication started on slot: %s", sess.slotName) 207 | return nil 208 | } 209 | 210 | // delete all old slots that were created by us 211 | func deleteAllSlots(conn *pgx.Conn, replConn *pgx.Conn) error { 212 | rows, err := conn.Query(context.Background(), "SELECT slot_name FROM pg_replication_slots") 213 | if err != nil { 214 | return err 215 | } 216 | for rows.Next() { 217 | var slotName string 218 | rows.Scan(&slotName) 219 | 220 | // only delete slots created by this program 221 | if !strings.Contains(slotName, "slot_") { 222 | continue 223 | } 224 | logger.Debugf("Deleting replication slot %s", slotName) 225 | err := pglogrepl.DropReplicationSlot( 226 | context.Background(), replConn.PgConn(), slotName, pglogrepl.DropReplicationSlotOptions{}, 227 | ) 228 | if err != nil { 229 | return errors.Wrapf(err, "could not delete slot %s", slotName) 230 | } 231 | } 232 | return nil 233 | } 234 | 235 | func StartSession( 236 | ctx context.Context, 237 | loggr Logger, 238 | dsn string, 239 | identifier string, 240 | connector CDCStreamer, 241 | startLSN pglogrepl.LSN, 242 | busyWaitInterval time.Duration, 243 | keepAlive time.Duration) (string, error) { 244 | 245 | if loggr != nil { 246 | logger = loggr 247 | } 248 | 249 | conn, err := pgx.Connect( 250 | ctx, dsn, 251 | ) 252 | 253 | if err != nil { 254 | return "", errors.Wrap(err, "Failed to connect to postgresql server in default context") 255 | } 256 | 257 | // Making replication conn 258 | replConn, err := pgx.Connect( 259 | ctx, 260 | fmt.Sprintf("%s replication=database", dsn), 261 | ) 262 | 263 | if err != nil { 264 | return "", errors.Wrap(err, "Failed to connect to postgresql server in replication context") 265 | } 266 | 267 | var slotName string 268 | if identifier == "" { 269 | slotName = generateSlotName() 270 | } else { 271 | slotName = identifier 272 | } 273 | 274 | sess := &session{ 275 | ctx: ctx, 276 | slotName: slotName, 277 | replConn: replConn, 278 | busyWaitInterval: busyWaitInterval, 279 | statusInterval: keepAlive, 280 | connector: connector, 281 | } 282 | activeSessions[sess.slotName] = sess 283 | 284 | if startLSN == 0 { 285 | sysident, err := pglogrepl.IdentifySystem(context.Background(), sess.PgConn()) 286 | if err != nil { 287 | return sess.slotName, errors.Wrap( 288 | err, "Unable to find out the current lsn of the db. Cannot start cdc") 289 | } 290 | sess.nextLSN = sysident.XLogPos 291 | 292 | logger.Debug("SystemID:", sysident.SystemID, "Timeline:", sysident.Timeline, "XLogPos:", sysident.XLogPos, "DBName:", sysident.DBName) 293 | 294 | } else { 295 | sess.nextLSN = startLSN 296 | } 297 | initiateReplication(ctx, conn, sess) 298 | sess.nextStatusDeadline = time.Now().Add(sess.statusInterval) 299 | go streamReplicationMessages(ctx, sess) 300 | return sess.slotName, nil 301 | } 302 | 303 | func OnMessageProcessed( 304 | ctx context.Context, 305 | name string, 306 | walMessage *Wal2JsonMessage, 307 | ) error { 308 | var sess *session 309 | var ok bool 310 | if sess, ok = activeSessions[name]; !ok { 311 | return fmt.Errorf("No active session found with name: %s", name) 312 | } 313 | sess.nextLSN = walMessage.NextLSN 314 | sess.nextStatusDeadline = time.Now() 315 | //logger.Infof("Processed Message with LSN: %v, Updating the applied LSN to: %s", walMessage.WALStart, walMessage.NextLSN) 316 | return nil 317 | } 318 | -------------------------------------------------------------------------------- /channel_stream.go: -------------------------------------------------------------------------------- 1 | package cdc 2 | 3 | type ChannelStream struct { 4 | channel chan *Wal2JsonMessage 5 | isActive bool 6 | } 7 | 8 | func NewChannelStream() *ChannelStream { 9 | return &ChannelStream{ 10 | channel: make(chan *Wal2JsonMessage), 11 | isActive: true, 12 | } 13 | } 14 | 15 | func (stream *ChannelStream) Send(walMessage *Wal2JsonMessage) { 16 | stream.channel <- walMessage 17 | } 18 | 19 | func (stream *ChannelStream) Receive() (*Wal2JsonMessage, error) { 20 | return <-stream.channel, nil 21 | } 22 | 23 | func (stream *ChannelStream) Close() error { 24 | close(stream.channel) 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vertexcover-io/postgresql-cdc 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/jackc/pgconn v1.8.0 7 | github.com/jackc/pglogrepl v0.0.0-20210109153808-a78a685a0bff 8 | github.com/jackc/pgproto3/v2 v2.0.7 9 | github.com/jackc/pgx/v4 v4.10.1 10 | github.com/pkg/errors v0.9.1 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 3 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 4 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 5 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 9 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 10 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 11 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 12 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 13 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 14 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 15 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 16 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 17 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 18 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 19 | github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= 20 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= 21 | github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= 22 | github.com/jackc/pgconn v1.6.5-0.20200823013804-5db484908cf7/go.mod h1:gm9GeeZiC+Ja7JV4fB/MNDeaOqsCrzFiZlLVhAompxk= 23 | github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw= 24 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 25 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 26 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 27 | github.com/jackc/pglogrepl v0.0.0-20210109153808-a78a685a0bff h1:LrGUmpk9sSQNdCcROjXj6iWXntQ/w4OTtINtyj//5RM= 28 | github.com/jackc/pglogrepl v0.0.0-20210109153808-a78a685a0bff/go.mod h1:DmTlVuDAzLCpHDCtr+UJOGjN09Lh/7AvCULTvbRt674= 29 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 30 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 31 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 32 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 33 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 34 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 35 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 36 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 37 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 38 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 39 | github.com/jackc/pgproto3/v2 v2.0.4/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 40 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 41 | github.com/jackc/pgproto3/v2 v2.0.7 h1:6Pwi1b3QdY65cuv6SyVO0FgPd5J3Bl7wf/nQQjinHMA= 42 | github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 43 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 44 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 45 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 46 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 47 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 48 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 49 | github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= 50 | github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= 51 | github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= 52 | github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8= 53 | github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= 54 | github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= 55 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 56 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 57 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 58 | github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= 59 | github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= 60 | github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= 61 | github.com/jackc/pgx/v4 v4.10.1 h1:/6Q3ye4myIj6AaplUm+eRcz4OhK9HAvFf4ePsG40LJY= 62 | github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= 63 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 64 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 65 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 66 | github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 67 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 68 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 69 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 70 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 71 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 72 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 73 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 74 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 75 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 76 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 77 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 78 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 79 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 80 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 81 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 82 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 83 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 84 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 85 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 86 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 87 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 88 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 89 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 90 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 91 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 92 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 93 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 94 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 95 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 96 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 97 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 98 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 99 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 100 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 101 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 102 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 103 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 104 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 105 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 106 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 107 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 108 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 109 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 110 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 111 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 112 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 113 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 114 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 115 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 116 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 117 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 118 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 119 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 120 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 121 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 122 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 123 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 124 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 125 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 126 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 127 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 128 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 129 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 130 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 131 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 132 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 133 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 134 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 135 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 136 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 137 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 140 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 142 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 143 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 144 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 145 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 146 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 147 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 148 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 149 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 150 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 151 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 152 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 153 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 154 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 155 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 156 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 157 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 158 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 159 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 160 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 161 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 162 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 163 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 164 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package cdc 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | ) 8 | 9 | type logLevel int 10 | 11 | const ( 12 | DEBUG = iota 13 | INFO 14 | WARNING 15 | ERROR 16 | PANIC 17 | FATAL 18 | NEVER_LOG 19 | ) 20 | 21 | var LEVEL_TO_STR = map[int]string{ 22 | DEBUG: "debug", 23 | INFO: "info", 24 | WARNING: "warning", 25 | ERROR: "error", 26 | PANIC: "panic", 27 | FATAL: "fatal", 28 | NEVER_LOG: "never_log", 29 | } 30 | 31 | var STR_TO_LEVEL = map[string]int{ 32 | "debug": DEBUG, 33 | "info": INFO, 34 | "warning": WARNING, 35 | "warn": WARNING, 36 | "error": ERROR, 37 | "panic": PANIC, 38 | "fatal": FATAL, 39 | } 40 | 41 | var logger Logger 42 | 43 | func init() { 44 | logger = newDefaultLogger() 45 | } 46 | 47 | func ParseLevel(level string) int { 48 | return STR_TO_LEVEL[level] 49 | } 50 | func LevelToString(level int) string { 51 | return LEVEL_TO_STR[level] 52 | } 53 | 54 | type Logger interface { 55 | Debug(...interface{}) 56 | Info(...interface{}) 57 | Warn(...interface{}) 58 | Error(...interface{}) 59 | Fatal(...interface{}) 60 | Panic(...interface{}) 61 | SetLevel(int) 62 | Debugf(string, ...interface{}) 63 | Infof(string, ...interface{}) 64 | Warnf(string, ...interface{}) 65 | Errorf(string, ...interface{}) 66 | Fatalf(string, ...interface{}) 67 | Panicf(string, ...interface{}) 68 | } 69 | 70 | type defaultLogger struct { 71 | logger *log.Logger 72 | minLevel int 73 | } 74 | 75 | func newDefaultLogger() *defaultLogger { 76 | return &defaultLogger{ 77 | logger: log.New(os.Stdout, "", log.LstdFlags), 78 | minLevel: WARNING, 79 | } 80 | } 81 | 82 | func (d *defaultLogger) prepareMsg(level int, msg string) string { 83 | return fmt.Sprintf("level=%s message=%s", LEVEL_TO_STR[level], msg) 84 | } 85 | 86 | func (d *defaultLogger) logf(level int, msg string, v ...interface{}) { 87 | if d.minLevel <= level { 88 | msg = d.prepareMsg(level, msg) 89 | d.logger.Printf(msg, v...) 90 | } 91 | } 92 | 93 | func (d *defaultLogger) log(level int, v ...interface{}) { 94 | if d.minLevel <= level { 95 | msg := d.prepareMsg(level, "") 96 | d.logger.Print(append([]interface{}{msg}, v...)...) 97 | } 98 | } 99 | 100 | func (d *defaultLogger) SetLevel(level int) { 101 | if level < DEBUG || level > NEVER_LOG { 102 | panic(fmt.Sprintf("Invalid Log Level: %d", level)) 103 | } 104 | d.minLevel = level 105 | } 106 | 107 | func (d *defaultLogger) Debug(v ...interface{}) { 108 | d.log(DEBUG, v...) 109 | } 110 | 111 | func (d *defaultLogger) Info(v ...interface{}) { 112 | d.log(INFO, v...) 113 | } 114 | func (d *defaultLogger) Warn(v ...interface{}) { 115 | d.log(WARNING, v...) 116 | } 117 | func (d *defaultLogger) Error(v ...interface{}) { 118 | d.log(ERROR, v...) 119 | } 120 | 121 | func (d *defaultLogger) Panic(v ...interface{}) { 122 | if d.minLevel <= PANIC { 123 | msg := d.prepareMsg(PANIC, "") 124 | d.logger.Panic(append([]interface{}{msg}, v...)...) 125 | } 126 | } 127 | 128 | func (d *defaultLogger) Fatal(v ...interface{}) { 129 | if d.minLevel <= FATAL { 130 | msg := d.prepareMsg(FATAL, "") 131 | d.logger.Fatal(append([]interface{}{msg}, v...)...) 132 | } 133 | } 134 | 135 | func (d *defaultLogger) Debugf(msg string, v ...interface{}) { 136 | d.logf(DEBUG, msg, v...) 137 | } 138 | 139 | func (d *defaultLogger) Infof(msg string, v ...interface{}) { 140 | d.logf(INFO, msg, v...) 141 | } 142 | func (d *defaultLogger) Warnf(msg string, v ...interface{}) { 143 | d.logf(WARNING, msg, v...) 144 | } 145 | func (d *defaultLogger) Errorf(msg string, v ...interface{}) { 146 | d.logf(ERROR, msg, v...) 147 | } 148 | func (d *defaultLogger) Panicf(msg string, v ...interface{}) { 149 | if d.minLevel <= PANIC { 150 | msg = d.prepareMsg(PANIC, msg) 151 | d.logger.Panicf(msg, v...) 152 | } 153 | } 154 | func (d *defaultLogger) Fatalf(msg string, v ...interface{}) { 155 | if d.minLevel <= FATAL { 156 | msg = d.prepareMsg(FATAL, msg) 157 | d.logger.Panicf(msg, v...) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package cdc 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | 8 | "github.com/jackc/pgconn" 9 | "github.com/jackc/pglogrepl" 10 | "github.com/jackc/pgx/v4" 11 | ) 12 | 13 | // taken from https://github.com/chobostar/pg_listener 14 | 15 | const MsgTimestampFormat = "2006-01-02 15:04:05.999999-07" 16 | 17 | type CDCStreamer interface { 18 | Send(walMessage *Wal2JsonMessage) 19 | Receive() (*Wal2JsonMessage, error) 20 | Close() error 21 | } 22 | 23 | type jsonMessage struct { 24 | Change []Wal2JsonChange `json:"change"` 25 | Timestamp string `json:"timestamp"` 26 | NextLSN string `json:"nextlsn"` 27 | } 28 | 29 | type Wal2JsonMessage struct { 30 | Change []Wal2JsonChange `json:"change"` 31 | Timestamp time.Time `json:"timestamp"` 32 | NextLSN pglogrepl.LSN `json:"nextlsn"` 33 | WALStart pglogrepl.LSN `json:"-"` 34 | } 35 | 36 | func (w *Wal2JsonMessage) UnmarshalJSON(data []byte) error { 37 | msg := jsonMessage{} 38 | var err error 39 | if err = json.Unmarshal(data, &msg); err != nil { 40 | return err 41 | } 42 | w.Change = msg.Change 43 | if w.Timestamp, err = time.Parse(MsgTimestampFormat, msg.Timestamp); err != nil { 44 | return err 45 | } 46 | 47 | if w.NextLSN, err = pglogrepl.ParseLSN(msg.NextLSN); err != nil { 48 | return err 49 | } 50 | return nil 51 | } 52 | 53 | //Wal2JsonChange defines children of root documents 54 | type Wal2JsonChange struct { 55 | Kind string `json:"kind"` 56 | Schema string `json:"schema"` 57 | Table string `json:"table"` 58 | ColumnNames []string `json:"columnnames"` 59 | ColumnTypes []string `json:"columntypes"` 60 | ColumnValues []interface{} `json:"columnvalues"` 61 | OldKeys Wal2JsonOldKeys `json:"oldkeys"` 62 | } 63 | 64 | //Wal2JsonOldKeys defines children of OldKeys 65 | type Wal2JsonOldKeys struct { 66 | KeyNames []string `json:"keynames"` 67 | KeyTypes []string `json:"keytypes"` 68 | KeyValues []interface{} `json:"keyvalues"` 69 | } 70 | 71 | type session struct { 72 | ctx context.Context 73 | replConn *pgx.Conn 74 | connector CDCStreamer 75 | statusInterval time.Duration 76 | nextStatusDeadline time.Time 77 | nextLSN pglogrepl.LSN 78 | slotName string 79 | busyWaitInterval time.Duration 80 | } 81 | 82 | func (sess *session) PgConn() *pgconn.PgConn { 83 | return sess.replConn.PgConn() 84 | } 85 | --------------------------------------------------------------------------------