2020-10-17 10:13:15 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/csv"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
|
|
|
"git.nutfactory.org/hoernschen/ActivityPub/entities/activity"
|
|
|
|
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
|
|
|
"git.nutfactory.org/hoernschen/ActivityPub/entities/user"
|
|
|
|
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
var BaseLineTest = false
|
|
|
|
|
|
|
|
var systemParamsIndex = 0
|
|
|
|
|
|
|
|
type SystemParams struct {
|
|
|
|
Id string
|
|
|
|
BytesToSend int
|
|
|
|
MessagesPerSecond float32
|
|
|
|
Distribution map[string][]string
|
|
|
|
Packetloss int
|
|
|
|
MinutesNotAvailable int
|
|
|
|
Consensus bool
|
|
|
|
AuthentificationCheck bool
|
|
|
|
Signing bool
|
|
|
|
Encryption bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var httpString string
|
|
|
|
var users = []map[string][]string{
|
|
|
|
map[string][]string{
|
|
|
|
"143.93.38.208": []string{
|
|
|
|
"user1",
|
|
|
|
"user2",
|
|
|
|
"user3",
|
|
|
|
"user4",
|
|
|
|
"user5",
|
|
|
|
"user6",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
map[string][]string{
|
|
|
|
"143.93.38.207": []string{
|
|
|
|
"user1",
|
|
|
|
"user2",
|
|
|
|
"user3",
|
|
|
|
"user4",
|
|
|
|
},
|
|
|
|
"143.93.38.208": []string{
|
|
|
|
"user1",
|
|
|
|
},
|
|
|
|
"143.93.38.209": []string{
|
|
|
|
"user1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
map[string][]string{
|
|
|
|
"143.93.38.207": []string{
|
|
|
|
"user1",
|
|
|
|
"user2",
|
|
|
|
"user3",
|
|
|
|
"user4",
|
|
|
|
},
|
|
|
|
"143.93.38.208": []string{
|
|
|
|
"user1",
|
|
|
|
"user2",
|
|
|
|
"user3",
|
|
|
|
"user4",
|
|
|
|
},
|
|
|
|
"143.93.38.209": []string{
|
|
|
|
"user1",
|
|
|
|
"user2",
|
|
|
|
"user3",
|
|
|
|
"user4",
|
|
|
|
},
|
|
|
|
},
|
2020-11-27 04:46:17 +00:00
|
|
|
map[string][]string{
|
|
|
|
"localhost": []string{
|
|
|
|
"user1",
|
|
|
|
"user2",
|
|
|
|
"user3",
|
|
|
|
"user4",
|
|
|
|
},
|
|
|
|
},
|
2020-10-17 10:13:15 +00:00
|
|
|
}
|
|
|
|
|
2020-11-27 04:46:17 +00:00
|
|
|
/*
|
2020-10-17 10:13:15 +00:00
|
|
|
var servers = []string{
|
|
|
|
"143.93.38.207",
|
|
|
|
"143.93.38.208",
|
|
|
|
"143.93.38.209",
|
|
|
|
}
|
2020-11-27 04:46:17 +00:00
|
|
|
*/
|
|
|
|
var servers = []string{
|
|
|
|
"localhost",
|
|
|
|
}
|
2020-10-17 10:13:15 +00:00
|
|
|
|
|
|
|
var systemParams = []SystemParams{
|
|
|
|
SystemParams{
|
2020-11-27 04:46:17 +00:00
|
|
|
Id: "111110101",
|
2020-10-17 10:13:15 +00:00
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
2020-11-27 04:46:17 +00:00
|
|
|
Distribution: users[3],
|
2020-10-17 10:13:15 +00:00
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
/*
|
|
|
|
SystemParams{
|
2020-11-27 04:46:17 +00:00
|
|
|
Id: "111110101",
|
2020-10-17 10:13:15 +00:00
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 1,
|
2020-11-27 04:46:17 +00:00
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "011110101",
|
|
|
|
BytesToSend: 8,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "211110101",
|
|
|
|
BytesToSend: 512,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "101110101",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 0.1,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "121110101",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 10.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "110110101",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[0],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "112110101",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[2],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "111010101",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 0,
|
|
|
|
MinutesNotAvailable: 0,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
|
|
|
SystemParams{
|
|
|
|
Id: "111210101",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 20,
|
|
|
|
MinutesNotAvailable: 0,
|
2020-10-17 10:13:15 +00:00
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
2020-11-27 04:46:17 +00:00
|
|
|
/*
|
|
|
|
SystemParams{
|
|
|
|
Id: "111120000",
|
|
|
|
BytesToSend: 280,
|
|
|
|
MessagesPerSecond: 1.0,
|
|
|
|
Distribution: users[1],
|
|
|
|
Packetloss: 1,
|
|
|
|
MinutesNotAvailable: 1,
|
|
|
|
Consensus: true,
|
|
|
|
AuthentificationCheck: true,
|
|
|
|
Signing: true,
|
|
|
|
Encryption: true,
|
|
|
|
},
|
2020-10-17 10:13:15 +00:00
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
var userIds []string
|
|
|
|
var accessTokens map[string]string
|
|
|
|
var collectionId string
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
|
|
httpString = "https"
|
|
|
|
iteration := 0
|
|
|
|
if BaseLineTest {
|
|
|
|
file, err := os.Create(fmt.Sprintf("%s Baseline Measurement ActivityPub.csv", strconv.FormatInt(time.Now().Unix(), 10)))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error Creating CSV: %s", err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
writer := csv.NewWriter(file)
|
|
|
|
defer writer.Flush()
|
|
|
|
err = writer.Write([]string{"Iteration", "Start", "End"})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error in Writing CSV: %s", err)
|
|
|
|
}
|
|
|
|
for iteration < 10 {
|
|
|
|
start := time.Now().Unix()
|
|
|
|
time.Sleep(2 * time.Minute)
|
|
|
|
end := time.Now().Unix()
|
|
|
|
iteration++
|
|
|
|
err = writer.Write([]string{strconv.Itoa(iteration), strconv.FormatInt(start, 10), strconv.FormatInt(end, 10)})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error in Writing CSV: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for _, systemParam := range systemParams {
|
|
|
|
file, err := os.Create(fmt.Sprintf("%s %s Measurement ActivityPub.csv", strconv.FormatInt(time.Now().Unix(), 10), systemParam.Id))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error Creating CSV: %s", err)
|
|
|
|
}
|
|
|
|
writer := csv.NewWriter(file)
|
|
|
|
err = writer.Write([]string{"Iteration", "Start", "End", "Actions Send"})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error in Writing CSV: %s", err)
|
|
|
|
}
|
|
|
|
millisecondsToWait := 1000 / systemParam.MessagesPerSecond
|
|
|
|
iteration = 0
|
|
|
|
for iteration < 30 {
|
|
|
|
err := setUp(systemParam)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error in SetUp: %s", err)
|
|
|
|
}
|
|
|
|
b := make([]byte, systemParam.BytesToSend)
|
|
|
|
_, err = rand.Read(b)
|
|
|
|
message := base64.RawStdEncoding.EncodeToString(b)
|
|
|
|
start := time.Now()
|
|
|
|
end := start.Add(2 * time.Minute).Unix()
|
|
|
|
log.Printf("Id: %s - Iteration: %s - Start: %s - End: %s", systemParam.Id, strconv.Itoa(iteration), strconv.FormatInt(start.Unix(), 10), strconv.FormatInt(end, 10))
|
|
|
|
actionsSend := 0
|
|
|
|
for time.Now().Unix() < end {
|
|
|
|
time.Sleep(time.Duration(millisecondsToWait) * time.Millisecond)
|
|
|
|
|
|
|
|
err = sendMessage(message)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error sending Message %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
actionsSend++
|
|
|
|
}
|
|
|
|
iteration++
|
|
|
|
err = writer.Write([]string{strconv.Itoa(iteration), strconv.FormatInt(start.Unix(), 10), strconv.FormatInt(end, 10), strconv.Itoa(actionsSend)})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error in Writing CSV: %s", err)
|
|
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
err = reset()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Error in Reset: %s", err)
|
|
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
}
|
|
|
|
writer.Flush()
|
|
|
|
file.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func sendMessage(message string) (err error) {
|
|
|
|
userId := userIds[0]
|
|
|
|
accessToken := accessTokens[userId]
|
|
|
|
requestUrl := fmt.Sprintf("%soutbox/", userId)
|
|
|
|
messageObject := object.Object{
|
|
|
|
Type: "Note",
|
|
|
|
Content: message,
|
|
|
|
}
|
|
|
|
reqBody, err := json.Marshal(messageObject)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
client := &http.Client{}
|
|
|
|
var req *http.Request
|
|
|
|
req, err = http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(reqBody))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
|
|
|
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", accessToken)}
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
handleError(res)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func setParams(systemParamsToUse SystemParams) (err error) {
|
|
|
|
serverNotAvailableIndex := 1
|
|
|
|
for i, server := range servers {
|
|
|
|
minutesNotAvailable := 0
|
|
|
|
if serverNotAvailableIndex == i {
|
|
|
|
minutesNotAvailable = systemParamsToUse.MinutesNotAvailable
|
|
|
|
}
|
|
|
|
requestUrl := fmt.Sprintf("%s://%s/setparams", httpString, server)
|
|
|
|
request := config.SetParamBody{
|
|
|
|
Packetloss: systemParamsToUse.Packetloss,
|
|
|
|
UnavailableTill: time.Now().Add(time.Duration(minutesNotAvailable) * time.Minute).Unix(),
|
|
|
|
Consensus: systemParamsToUse.Consensus,
|
|
|
|
AuthentificationCheck: systemParamsToUse.AuthentificationCheck,
|
|
|
|
Signing: systemParamsToUse.Signing,
|
|
|
|
Encryption: systemParamsToUse.Encryption,
|
|
|
|
}
|
|
|
|
var reqBody []byte
|
|
|
|
reqBody, err = json.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
|
|
var req *http.Request
|
|
|
|
req, err = http.NewRequest(http.MethodGet, requestUrl, bytes.NewBuffer(reqBody))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.Header["Content-Type"] = []string{"application/json"}
|
|
|
|
var res *http.Response
|
|
|
|
res, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
handleError(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func setUp(systemParamsToUse SystemParams) (err error) {
|
|
|
|
accessTokens = make(map[string]string)
|
|
|
|
err = createUsers(systemParamsToUse.Distribution)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error in User-Creation: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Follow User
|
|
|
|
log.Printf("Userids: %s", userIds)
|
|
|
|
err = followUser(userIds[0], userIds[1:])
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error follow User: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = setParams(systemParamsToUse)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func reset() (err error) {
|
|
|
|
userIds = []string{}
|
|
|
|
accessTokens = make(map[string]string)
|
|
|
|
for _, server := range servers {
|
|
|
|
requestUrl := fmt.Sprintf("%s://%s/reset", httpString, server)
|
|
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
|
|
var req *http.Request
|
|
|
|
req, err = http.NewRequest(http.MethodGet, requestUrl, bytes.NewBuffer(nil))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.Header["Content-Type"] = []string{"application/json"}
|
|
|
|
var res *http.Response
|
|
|
|
res, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
handleError(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func followUser(userIdToFollow string, userIdsThatFollow []string) (err error) {
|
|
|
|
for _, userId := range userIdsThatFollow {
|
|
|
|
log.Printf("UserIdThatFollow: %s", userId)
|
|
|
|
accessToken := accessTokens[userId]
|
|
|
|
requestUrl := fmt.Sprintf("%soutbox/", userId)
|
|
|
|
followActivity := activity.Activity{
|
|
|
|
Type: "Follow",
|
|
|
|
Actor: userId,
|
|
|
|
To: userIdToFollow,
|
|
|
|
}
|
|
|
|
var reqBody []byte
|
|
|
|
reqBody, err = json.Marshal(followActivity)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
|
|
var req *http.Request
|
|
|
|
req, err = http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(reqBody))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
|
|
|
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", accessToken)}
|
|
|
|
var res *http.Response
|
|
|
|
res, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
err = utils.HandleHTTPError(res)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
//time.Sleep(time.Duration(1) * time.Second)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func createUsers(serverUserMap map[string][]string) (err error) {
|
|
|
|
log.Println("Create Users")
|
|
|
|
for server, usersToCreate := range serverUserMap {
|
|
|
|
for _, userToCreate := range usersToCreate {
|
|
|
|
var userId string
|
|
|
|
var accessToken string
|
|
|
|
userId, accessToken, err = createUser(userToCreate, server)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if userId != "" && accessToken != "" {
|
|
|
|
log.Printf("%s created - AccessToken: %s", userId, accessToken)
|
|
|
|
accessTokens[userId] = accessToken
|
|
|
|
userIds = append(userIds, userId)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Duration(20) * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func createUser(userToCreate string, homeserver string) (userId string, accessToken string, err error) {
|
|
|
|
requestUrl := fmt.Sprintf("%s://%s/register", httpString, homeserver)
|
|
|
|
request := user.RegisterRequest{
|
|
|
|
Username: userToCreate,
|
|
|
|
Password: "password",
|
|
|
|
}
|
|
|
|
reqBody, err := json.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
client := &http.Client{Timeout: 2 * time.Second}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, requestUrl, bytes.NewBuffer(reqBody))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
err = utils.HandleHTTPError(res)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
response := user.RegisterResponse{}
|
|
|
|
decoder := json.NewDecoder(res.Body)
|
|
|
|
err = decoder.Decode(&response)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
userId = response.Actor
|
|
|
|
accessToken = response.Token
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleError(res *http.Response) {
|
|
|
|
err := utils.HandleHTTPError(res)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("%s", err)
|
|
|
|
}
|
|
|
|
}
|